bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSamples) { if (totalSamples == 0) { return false; } bool bPreferencesBeatDetectionEnabled = m_pConfig->getValue<bool>( ConfigKey(BPM_CONFIG_KEY, BPM_DETECTION_ENABLED)); if (!bPreferencesBeatDetectionEnabled) { qDebug() << "Beat calculation is deactivated"; return false; } bool bpmLock = tio->isBpmLocked(); if (bpmLock) { qDebug() << "Track is BpmLocked: Beat calculation will not start"; return false; } bool allow_above = m_pConfig->getValue<bool>( ConfigKey(BPM_CONFIG_KEY, BPM_ABOVE_RANGE_ENABLED)); if (allow_above) { m_iMinBpm = 0; m_iMaxBpm = 9999; } else { m_iMinBpm = m_pConfig->getValueString(ConfigKey(BPM_CONFIG_KEY, BPM_RANGE_START)).toInt(); m_iMaxBpm = m_pConfig->getValueString(ConfigKey(BPM_CONFIG_KEY, BPM_RANGE_END)).toInt(); } m_bPreferencesFixedTempo = m_pConfig->getValue<bool>( ConfigKey(BPM_CONFIG_KEY, BPM_FIXED_TEMPO_ASSUMPTION)); m_bPreferencesOffsetCorrection = m_pConfig->getValue<bool>( ConfigKey(BPM_CONFIG_KEY, BPM_FIXED_TEMPO_OFFSET_CORRECTION)); m_bPreferencesReanalyzeOldBpm = m_pConfig->getValue<bool>( ConfigKey(BPM_CONFIG_KEY, BPM_REANALYZE_WHEN_SETTINGS_CHANGE)); m_bPreferencesFastAnalysis = m_pConfig->getValue<bool>( ConfigKey(BPM_CONFIG_KEY, BPM_FAST_ANALYSIS_ENABLED)); QString library = m_pConfig->getValueString( ConfigKey(VAMP_CONFIG_KEY, VAMP_ANALYZER_BEAT_LIBRARY)); QString pluginID = m_pConfig->getValueString( ConfigKey(VAMP_CONFIG_KEY, VAMP_ANALYZER_BEAT_PLUGIN_ID)); m_pluginId = pluginID; m_iSampleRate = sampleRate; m_iTotalSamples = totalSamples; // if we can load a stored track don't reanalyze it bool bShouldAnalyze = !isDisabledOrLoadStoredSuccess(tio); if (bShouldAnalyze) { m_pVamp = new VampAnalyzer(); bShouldAnalyze = m_pVamp->Init(library, pluginID, m_iSampleRate, totalSamples, m_bPreferencesFastAnalysis); if (!bShouldAnalyze) { delete m_pVamp; m_pVamp = NULL; } } if (bShouldAnalyze) { qDebug() << "Beat calculation started with plugin" << pluginID; } else { qDebug() << "Beat calculation will not start"; } return bShouldAnalyze; }
void WaveformRendererHSV::draw(QPainter* painter, QPaintEvent* /*event*/) { const TrackPointer trackInfo = m_waveformRenderer->getTrackInfo(); if (!trackInfo) { return; } const Waveform* waveform = trackInfo->getWaveform(); if (waveform == NULL) { return; } const int dataSize = waveform->getDataSize(); if (dataSize <= 1) { return; } const WaveformData* data = waveform->data(); if (data == NULL) { return; } painter->save(); painter->setRenderHints(QPainter::Antialiasing, false); painter->setRenderHints(QPainter::HighQualityAntialiasing, false); painter->setRenderHints(QPainter::SmoothPixmapTransform, false); painter->setWorldMatrixEnabled(false); painter->resetTransform(); const double firstVisualIndex = m_waveformRenderer->getFirstDisplayedPosition() * dataSize; const double lastVisualIndex = m_waveformRenderer->getLastDisplayedPosition() * dataSize; const double offset = firstVisualIndex; // Represents the # of waveform data points per horizontal pixel. const double gain = (lastVisualIndex - firstVisualIndex) / (double)m_waveformRenderer->getWidth(); float allGain(1.0); getGains(&allGain, NULL, NULL, NULL); // Save HSV of waveform color. NOTE(rryan): On ARM, qreal is float so it's // important we use qreal here and not double or float or else we will get // build failures on ARM. qreal h, s, v; // Get base color of waveform in the HSV format (s and v isn't use) m_pColors->getLowColor().getHsvF(&h, &s, &v); QColor color; float lo, hi, total; const float halfHeight = (float)m_waveformRenderer->getHeight()/2.0; const float heightFactor = allGain*halfHeight/255.0; //draw reference line painter->setPen(m_pColors->getAxesColor()); painter->drawLine(0,halfHeight,m_waveformRenderer->getWidth(),halfHeight); for (int x = 0; x < m_waveformRenderer->getWidth(); ++x) { // Width of the x position in visual indices. const double xSampleWidth = gain * x; // Effective visual index of x const double xVisualSampleIndex = xSampleWidth + offset; // Our current pixel (x) corresponds to a number of visual samples // (visualSamplerPerPixel) in our waveform object. We take the max of // all the data points on either side of xVisualSampleIndex within a // window of 'maxSamplingRange' visual samples to measure the maximum // data point contained by this pixel. double maxSamplingRange = gain / 2.0; // Since xVisualSampleIndex is in visual-samples (e.g. R,L,R,L) we want // to check +/- maxSamplingRange frames, not samples. To do this, divide // xVisualSampleIndex by 2. Since frames indices are integers, we round // to the nearest integer by adding 0.5 before casting to int. int visualFrameStart = int(xVisualSampleIndex / 2.0 - maxSamplingRange + 0.5); int visualFrameStop = int(xVisualSampleIndex / 2.0 + maxSamplingRange + 0.5); const int lastVisualFrame = dataSize / 2 - 1; // We now know that some subset of [visualFrameStart, visualFrameStop] // lies within the valid range of visual frames. Clamp // visualFrameStart/Stop to within [0, lastVisualFrame]. visualFrameStart = math_clamp(visualFrameStart, 0, lastVisualFrame); visualFrameStop = math_clamp(visualFrameStop, 0, lastVisualFrame); int visualIndexStart = visualFrameStart * 2; int visualIndexStop = visualFrameStop * 2; int maxLow[2] = {0, 0}; int maxHigh[2] = {0, 0}; int maxMid[2] = {0, 0}; int maxAll[2] = {0, 0}; for (int i = visualIndexStart; i >= 0 && i + 1 < dataSize && i + 1 <= visualIndexStop; i += 2) { const WaveformData& waveformData = *(data + i); const WaveformData& waveformDataNext = *(data + i + 1); maxLow[0] = math_max(maxLow[0], (int)waveformData.filtered.low); maxLow[1] = math_max(maxLow[1], (int)waveformDataNext.filtered.low); maxMid[0] = math_max(maxMid[0], (int)waveformData.filtered.mid); maxMid[1] = math_max(maxMid[1], (int)waveformDataNext.filtered.mid); maxHigh[0] = math_max(maxHigh[0], (int)waveformData.filtered.high); maxHigh[1] = math_max(maxHigh[1], (int)waveformDataNext.filtered.high); maxAll[0] = math_max(maxAll[0], (int)waveformData.filtered.all); maxAll[1] = math_max(maxAll[1], (int)waveformDataNext.filtered.all); } if (maxAll[0] && maxAll[1]) { // Calculate sum, to normalize // Also multiply on 1.2 to prevent very dark or light color total = (maxLow[0] + maxLow[1] + maxMid[0] + maxMid[1] + maxHigh[0] + maxHigh[1]) * 1.2; // prevent division by zero if (total > 0) { // Normalize low and high (mid not need, because it not change the color) lo = (maxLow[0] + maxLow[1]) / total; hi = (maxHigh[0] + maxHigh[1]) / total; } else lo = hi = 0.0; // Set color color.setHsvF(h, 1.0-hi, 1.0-lo); painter->setPen(color); switch (m_alignment) { case Qt::AlignBottom : painter->drawLine( x, m_waveformRenderer->getHeight(), x, m_waveformRenderer->getHeight() - (int)(heightFactor*(float)math_max(maxAll[0],maxAll[1]))); break; case Qt::AlignTop : painter->drawLine( x, 0, x, (int)(heightFactor*(float)math_max(maxAll[0],maxAll[1]))); break; default : painter->drawLine( x, (int)(halfHeight-heightFactor*(float)maxAll[0]), x, (int)(halfHeight+heightFactor*(float)maxAll[1])); } } } painter->restore(); }
bool AnalyzerBeats::isDisabledOrLoadStoredSuccess(TrackPointer tio) const { int iMinBpm; int iMaxBpm; bool allow_above = m_pConfig->getValue<bool>( ConfigKey(BPM_CONFIG_KEY, BPM_ABOVE_RANGE_ENABLED)); if (allow_above) { iMinBpm = 0; iMaxBpm = 9999; } else { iMinBpm = m_pConfig->getValueString(ConfigKey(BPM_CONFIG_KEY, BPM_RANGE_START)).toInt(); iMaxBpm = m_pConfig->getValueString(ConfigKey(BPM_CONFIG_KEY, BPM_RANGE_END)).toInt(); } bool bpmLock = tio->isBpmLocked(); if (bpmLock) { qDebug() << "Track is BpmLocked: Beat calculation will not start"; return true; } QString library = m_pConfig->getValueString( ConfigKey(VAMP_CONFIG_KEY, VAMP_ANALYZER_BEAT_LIBRARY)); QString pluginID = m_pConfig->getValueString( ConfigKey(VAMP_CONFIG_KEY, VAMP_ANALYZER_BEAT_PLUGIN_ID)); // At first start config for QM and Vamp does not exist --> set default // TODO(XXX): This is no longer present in initialize. Remove? if (library.isEmpty() || library.isNull()) library = "libmixxxminimal"; if (pluginID.isEmpty() || pluginID.isNull()) pluginID = "qm-tempotracker:0"; // If the track already has a Beats object then we need to decide whether to // analyze this track or not. BeatsPointer pBeats = tio->getBeats(); if (pBeats) { QString version = pBeats->getVersion(); QString subVersion = pBeats->getSubVersion(); QHash<QString, QString> extraVersionInfo = getExtraVersionInfo( pluginID, m_bPreferencesFastAnalysis); QString newVersion = BeatFactory::getPreferredVersion( m_bPreferencesOffsetCorrection); QString newSubVersion = BeatFactory::getPreferredSubVersion( m_bPreferencesFixedTempo, m_bPreferencesOffsetCorrection, iMinBpm, iMaxBpm, extraVersionInfo); if (version == newVersion && subVersion == newSubVersion) { // If the version and settings have not changed then if the world is // sane, re-analyzing will do nothing. return true; } else if (m_bPreferencesReanalyzeOldBpm) { return false; } else if (pBeats->getBpm() == 0.0) { qDebug() << "BPM is 0 for track so re-analyzing despite preference settings."; return false; } else if (pBeats->findNextBeat(0) <= 0.0) { qDebug() << "First beat is 0 for grid so analyzing track to find first beat."; return false; } else { qDebug() << "Beat calculation skips analyzing because the track has" << "a BPM computed by a previous Mixxx version and user" << "preferences indicate we should not change it."; return true; } } else { // If we got here, we want to analyze this track. return false; } }
void AnalyzerBeats::finalize(TrackPointer tio) { if (m_pVamp == NULL) { return; } // Call End() here, because the number of total samples may have been // estimated incorrectly. bool success = m_pVamp->End(); qDebug() << "Beat Calculation" << (success ? "complete" : "failed"); QVector<double> beats = m_pVamp->GetInitFramesVector(); delete m_pVamp; m_pVamp = NULL; if (beats.isEmpty()) { qDebug() << "Could not detect beat positions from Vamp."; return; } QHash<QString, QString> extraVersionInfo = getExtraVersionInfo( m_pluginId, m_bPreferencesFastAnalysis); BeatsPointer pBeats = BeatFactory::makePreferredBeats( *tio, beats, extraVersionInfo, m_bPreferencesFixedTempo, m_bPreferencesOffsetCorrection, m_iSampleRate, m_iTotalSamples, m_iMinBpm, m_iMaxBpm); BeatsPointer pCurrentBeats = tio->getBeats(); // If the track has no beats object then set our newly generated one // regardless of beat lock. if (!pCurrentBeats) { tio->setBeats(pBeats); return; } // If the track received the beat lock while we were analyzing it then we // abort setting it. if (tio->isBpmLocked()) { qDebug() << "Track was BPM-locked as we were analyzing it. Aborting analysis."; return; } // If the user prefers to replace old beatgrids with newly generated ones or // the old beatgrid has 0-bpm then we replace it. bool zeroCurrentBpm = pCurrentBeats->getBpm() == 0.0; if (m_bPreferencesReanalyzeOldBpm || zeroCurrentBpm) { if (zeroCurrentBpm) { qDebug() << "Replacing 0-BPM beatgrid with a" << pBeats->getBpm() << "beatgrid."; } tio->setBeats(pBeats); return; } // If we got here then the user doesn't want to replace the beatgrid but // since the first beat is zero we'll apply the offset we just detected. double currentFirstBeat = pCurrentBeats->findNextBeat(0); double newFirstBeat = pBeats->findNextBeat(0); if (currentFirstBeat == 0.0 && newFirstBeat > 0) { pCurrentBeats->translate(newFirstBeat); } }
void GLWaveformRendererRGB::draw(QPainter* painter, QPaintEvent* /*event*/) { TrackPointer pTrack = m_waveformRenderer->getTrackInfo(); if (!pTrack) { return; } ConstWaveformPointer waveform = pTrack->getWaveform(); if (waveform.isNull()) { return; } const int dataSize = waveform->getDataSize(); if (dataSize <= 1) { return; } const WaveformData* data = waveform->data(); if (data == NULL) { return; } double firstVisualIndex = m_waveformRenderer->getFirstDisplayedPosition() * dataSize; double lastVisualIndex = m_waveformRenderer->getLastDisplayedPosition() * dataSize; const int firstIndex = int(firstVisualIndex + 0.5); firstVisualIndex = firstIndex - firstIndex % 2; const int lastIndex = int(lastVisualIndex + 0.5); lastVisualIndex = lastIndex + lastIndex % 2; // Reset device for native painting painter->beginNativePainting(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Per-band gain from the EQ knobs. float allGain(1.0), lowGain(1.0), midGain(1.0), highGain(1.0); getGains(&allGain, &lowGain, &midGain, &highGain); const float kHeightScaleFactor = 255.0 / sqrtf(255 * 255 * 3); #ifndef __OPENGLES__ if (m_alignment == Qt::AlignCenter) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(firstVisualIndex, lastVisualIndex, -255.0, 255.0, -10.0, 10.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glScalef(1.0f, allGain, 1.0f); glLineWidth(1.2); glDisable(GL_LINE_SMOOTH); // Draw reference line glBegin(GL_LINES); { glColor4f(m_axesColor_r, m_axesColor_g, m_axesColor_b, m_axesColor_a); glVertex2f(firstVisualIndex, 0); glVertex2f(lastVisualIndex, 0); } glEnd(); glLineWidth(2.0); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINES); { for (int visualIndex = firstVisualIndex; visualIndex < lastVisualIndex; visualIndex += 2) { if (visualIndex < 0) { continue; } if (visualIndex > dataSize - 1) { break; } float left_low = lowGain * (float) data[visualIndex].filtered.low; float left_mid = midGain * (float) data[visualIndex].filtered.mid; float left_high = highGain * (float) data[visualIndex].filtered.high; float left_all = sqrtf(left_low * left_low + left_mid * left_mid + left_high * left_high) * kHeightScaleFactor; float left_red = left_low * m_rgbLowColor_r + left_mid * m_rgbMidColor_r + left_high * m_rgbHighColor_r; float left_green = left_low * m_rgbLowColor_g + left_mid * m_rgbMidColor_g + left_high * m_rgbHighColor_g; float left_blue = left_low * m_rgbLowColor_b + left_mid * m_rgbMidColor_b + left_high * m_rgbHighColor_b; float left_max = math_max3(left_red, left_green, left_blue); if (left_max > 0.0f) { // Prevent division by zero glColor4f(left_red / left_max, left_green / left_max, left_blue / left_max, 0.8f); glVertex2f(visualIndex, 0.0f); glVertex2f(visualIndex, left_all); } float right_low = lowGain * (float) data[visualIndex+1].filtered.low; float right_mid = midGain * (float) data[visualIndex+1].filtered.mid; float right_high = highGain * (float) data[visualIndex+1].filtered.high; float right_all = sqrtf(right_low * right_low + right_mid * right_mid + right_high * right_high) * kHeightScaleFactor; float right_red = right_low * m_rgbLowColor_r + right_mid * m_rgbMidColor_r + right_high * m_rgbHighColor_r; float right_green = right_low * m_rgbLowColor_g + right_mid * m_rgbMidColor_g + right_high * m_rgbHighColor_g; float right_blue = right_low * m_rgbLowColor_b + right_mid * m_rgbMidColor_b + right_high * m_rgbHighColor_b; float right_max = math_max3(right_red, right_green, right_blue); if (right_max > 0.0f) { // Prevent division by zero glColor4f(right_red / right_max, right_green / right_max, right_blue / right_max, 0.8f); glVertex2f(visualIndex, 0.0f); glVertex2f(visualIndex, -1.0f * right_all); } } } glEnd(); } else { // top || bottom glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); if (m_alignment == Qt::AlignBottom) { glOrtho(firstVisualIndex, lastVisualIndex, 0.0, 255.0, -10.0, 10.0); } else { glOrtho(firstVisualIndex, lastVisualIndex, 255.0, 0.0, -10.0, 10.0); } glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glScalef(1.0f, allGain, 1.0f); glLineWidth(2.0); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINES); { for (int visualIndex = firstVisualIndex; visualIndex < lastVisualIndex; visualIndex += 2) { if (visualIndex < 0) { continue; } if (visualIndex > dataSize - 1) { break; } float low = lowGain * (float) math_max(data[visualIndex].filtered.low, data[visualIndex+1].filtered.low); float mid = midGain * (float) math_max(data[visualIndex].filtered.mid, data[visualIndex+1].filtered.mid); float high = highGain * (float) math_max(data[visualIndex].filtered.high, data[visualIndex+1].filtered.high); float all = sqrtf(low * low + mid * mid + high * high) * kHeightScaleFactor; float red = low * m_rgbLowColor_r + mid * m_rgbMidColor_r + high * m_rgbHighColor_r; float green = low * m_rgbLowColor_g + mid * m_rgbMidColor_g + high * m_rgbHighColor_g; float blue = low * m_rgbLowColor_b + mid * m_rgbMidColor_b + high * m_rgbHighColor_b; float max = math_max3(red, green, blue); if (max > 0.0f) { // Prevent division by zero glColor4f(red / max, green / max, blue / max, 0.9f); glVertex2f(float(visualIndex), 0.0f); glVertex2f(float(visualIndex), all); } } } glEnd(); } glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); #endif painter->endNativePainting(); }
void QtWaveformRendererSimpleSignal::draw(QPainter* painter, QPaintEvent* /*event*/){ TrackPointer pTrack = m_waveformRenderer->getTrackInfo(); if (!pTrack) { return; } const Waveform* waveform = pTrack->getWaveform(); if (waveform == NULL) { return; } const int dataSize = waveform->getDataSize(); if (dataSize <= 1) { return; } const WaveformData* data = waveform->data(); if (data == NULL) { return; } painter->save(); painter->setRenderHint(QPainter::Antialiasing); painter->resetTransform(); WaveformWidgetFactory* factory = WaveformWidgetFactory::instance(); const double visualGain = factory->getVisualGain(WaveformWidgetFactory::All); double heightGain = visualGain*m_waveformRenderer->getGain()*(double)m_waveformRenderer->getHeight()/255.0; if (m_alignment == Qt::AlignTop) { painter->translate(0.0, 0.0); painter->scale(1.0, heightGain); } else if (m_alignment == Qt::AlignBottom) { painter->translate(0.0, m_waveformRenderer->getHeight()); painter->scale(1.0, heightGain); } else { painter->translate(0.0, m_waveformRenderer->getHeight()/2.0); painter->scale(1.0, 0.5*heightGain); } //draw reference line if (m_alignment == Qt::AlignCenter) { painter->setPen(m_axesColor); painter->drawLine(0,0,m_waveformRenderer->getWidth(),0); } const double firstVisualIndex = m_waveformRenderer->getFirstDisplayedPosition() * dataSize; const double lastVisualIndex = m_waveformRenderer->getLastDisplayedPosition() * dataSize; m_polygon.clear(); m_polygon.reserve(2 * m_waveformRenderer->getWidth() + 2); m_polygon.append(QPointF(0.0, 0.0)); const double offset = firstVisualIndex; // Represents the # of waveform data points per horizontal pixel. const double gain = (lastVisualIndex - firstVisualIndex) / (double)m_waveformRenderer->getWidth(); //NOTE(vrince) Please help me find a better name for "channelSeparation" //this variable stand for merged channel ... 1 = merged & 2 = separated int channelSeparation = 2; if (m_alignment != Qt::AlignCenter) channelSeparation = 1; for (int channel = 0; channel < channelSeparation; ++channel) { int startPixel = 0; int endPixel = m_waveformRenderer->getWidth() - 1; int delta = 1; double direction = 1.0; //Reverse display for merged bottom channel if (m_alignment == Qt::AlignBottom) direction = -1.0; if (channel == 1) { startPixel = m_waveformRenderer->getWidth() - 1; endPixel = 0; delta = -1; direction = -1.0; // After preparing the first channel, insert the pivot point. m_polygon.append(QPointF(m_waveformRenderer->getWidth(), 0.0)); } for (int x = startPixel; (startPixel < endPixel) ? (x <= endPixel) : (x >= endPixel); x += delta) { // TODO(rryan) remove before 1.11 release. I'm seeing crashes // sometimes where the pointIndex is very very large. It hasn't come // back since adding locking, but I'm leaving this so that we can // get some info about it before crashing. (The crash usually // corrupts a lot of the stack). if (m_polygon.size() > 2 * m_waveformRenderer->getWidth() + 2) { qDebug() << "OUT OF CONTROL" << 2 * m_waveformRenderer->getWidth() + 2 << dataSize << channel << m_polygon.size() << x; } // Width of the x position in visual indices. const double xSampleWidth = gain * x; // Effective visual index of x const double xVisualSampleIndex = xSampleWidth + offset; // Our current pixel (x) corresponds to a number of visual samples // (visualSamplerPerPixel) in our waveform object. We take the max of // all the data points on either side of xVisualSampleIndex within a // window of 'maxSamplingRange' visual samples to measure the maximum // data point contained by this pixel. double maxSamplingRange = gain / 2.0; // Since xVisualSampleIndex is in visual-samples (e.g. R,L,R,L) we want // to check +/- maxSamplingRange frames, not samples. To do this, divide // xVisualSampleIndex by 2. Since frames indices are integers, we round // to the nearest integer by adding 0.5 before casting to int. int visualFrameStart = int(xVisualSampleIndex / 2.0 - maxSamplingRange + 0.5); int visualFrameStop = int(xVisualSampleIndex / 2.0 + maxSamplingRange + 0.5); // If the entire sample range is off the screen then don't calculate a // point for this pixel. const int lastVisualFrame = dataSize / 2 - 1; if (visualFrameStop < 0 || visualFrameStart > lastVisualFrame) { m_polygon.append(QPointF(x, 0.0)); continue; } // We now know that some subset of [visualFrameStart, // visualFrameStop] lies within the valid range of visual // frames. Clamp visualFrameStart/Stop to within [0, // lastVisualFrame]. visualFrameStart = math_max(math_min(lastVisualFrame, visualFrameStart), 0); visualFrameStop = math_max(math_min(lastVisualFrame, visualFrameStop), 0); int visualIndexStart = visualFrameStart * 2 + channel; int visualIndexStop = visualFrameStop * 2 + channel; // if (x == m_waveformRenderer->getWidth() / 2) { // qDebug() << "audioVisualRatio" << waveform->getAudioVisualRatio(); // qDebug() << "visualSampleRate" << waveform->getVisualSampleRate(); // qDebug() << "audioSamplesPerVisualPixel" << waveform->getAudioSamplesPerVisualSample(); // qDebug() << "visualSamplePerPixel" << visualSamplePerPixel; // qDebug() << "xSampleWidth" << xSampleWidth; // qDebug() << "xVisualSampleIndex" << xVisualSampleIndex; // qDebug() << "maxSamplingRange" << maxSamplingRange;; // qDebug() << "Sampling pixel " << x << "over [" << visualIndexStart << visualIndexStop << "]"; // } unsigned char maxAll = 0; for (int i = visualIndexStart; i >= 0 && i < dataSize && i <= visualIndexStop; i += channelSeparation) { const WaveformData& waveformData = *(data + i); unsigned char all = waveformData.filtered.all; maxAll = math_max(maxAll, all); } m_polygon.append(QPointF(x, (float)maxAll * direction)); } } //If channel are not displayed separatly we nne to close the loop properly if (channelSeparation == 1) { m_polygon.append(QPointF(m_waveformRenderer->getWidth(), 0.0)); } painter->setPen(m_borderPen); painter->setBrush(m_brush); painter->drawPolygon(&m_polygon[0], m_polygon.size()); painter->restore(); }
void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer track, bool bPlay) { // Before loading the track, ensure we have access. This uses lazy // evaluation to make sure track isn't NULL before we dereference it. if (!track.isNull() && !Sandbox::askForAccess(track->getCanonicalLocation())) { // We don't have access. return; } //Disconnect the old track's signals. if (m_pLoadedTrack) { // Save the loops that are currently set in a loop cue. If no loop cue is // currently on the track, then create a new one. int loopStart = m_pLoopInPoint->get(); int loopEnd = m_pLoopOutPoint->get(); if (loopStart != -1 && loopEnd != -1 && even(loopStart) && even(loopEnd) && loopStart <= loopEnd) { Cue* pLoopCue = NULL; QList<Cue*> cuePoints = m_pLoadedTrack->getCuePoints(); QListIterator<Cue*> it(cuePoints); while (it.hasNext()) { Cue* pCue = it.next(); if (pCue->getType() == Cue::LOOP) { pLoopCue = pCue; } } if (!pLoopCue) { pLoopCue = m_pLoadedTrack->addCue(); pLoopCue->setType(Cue::LOOP); } pLoopCue->setPosition(loopStart); pLoopCue->setLength(loopEnd - loopStart); } // WARNING: Never. Ever. call bare disconnect() on an object. Mixxx // relies on signals and slots to get tons of things done. Don't // randomly disconnect things. // m_pLoadedTrack->disconnect(); disconnect(m_pLoadedTrack.data(), 0, m_pBPM, 0); disconnect(m_pLoadedTrack.data(), 0, this, 0); disconnect(m_pLoadedTrack.data(), 0, m_pKey, 0); m_pReplayGain->slotSet(0); // Causes the track's data to be saved back to the library database. emit(unloadingTrack(m_pLoadedTrack)); } m_pLoadedTrack = track; if (m_pLoadedTrack) { // Listen for updates to the file's BPM connect(m_pLoadedTrack.data(), SIGNAL(bpmUpdated(double)), m_pBPM, SLOT(slotSet(double))); connect(m_pLoadedTrack.data(), SIGNAL(keyUpdated(double)), m_pKey, SLOT(slotSet(double))); // Listen for updates to the file's Replay Gain connect(m_pLoadedTrack.data(), SIGNAL(ReplayGainUpdated(double)), this, SLOT(slotSetReplayGain(double))); } //Request a new track from the reader emit(loadTrack(track, bPlay)); }
void BaseTrackPlayerImpl::slotFinishLoading(TrackPointer pTrackInfoObject) { m_replaygainPending = false; // Read the tags if required if (!m_pLoadedTrack->getHeaderParsed()) { m_pLoadedTrack->parse(false); } // m_pLoadedTrack->setPlayedAndUpdatePlaycount(true); // Actually the song is loaded but not played // Update the BPM and duration values that are stored in ControlObjects m_pDuration->set(m_pLoadedTrack->getDuration()); m_pBPM->slotSet(m_pLoadedTrack->getBpm()); m_pKey->slotSet(m_pLoadedTrack->getKey()); m_pReplayGain->slotSet(m_pLoadedTrack->getReplayGain()); // Update the PlayerInfo class that is used in EngineShoutcast to replace // the metadata of a stream PlayerInfo::instance().setTrackInfo(getGroup(), m_pLoadedTrack); // Reset the loop points. m_pLoopInPoint->slotSet(-1); m_pLoopOutPoint->slotSet(-1); const QList<Cue*> trackCues = pTrackInfoObject->getCuePoints(); QListIterator<Cue*> it(trackCues); while (it.hasNext()) { Cue* pCue = it.next(); if (pCue->getType() == Cue::LOOP) { int loopStart = pCue->getPosition(); int loopEnd = loopStart + pCue->getLength(); if (loopStart != -1 && loopEnd != -1 && even(loopStart) && even(loopEnd)) { m_pLoopInPoint->slotSet(loopStart); m_pLoopOutPoint->slotSet(loopEnd); break; } } } if(m_pConfig->getValueString(ConfigKey("[Mixer Profile]", "EqAutoReset"), 0).toInt()) { if (m_pLowFilter != NULL) { m_pLowFilter->set(1.0); } if (m_pMidFilter != NULL) { m_pMidFilter->set(1.0); } if (m_pHighFilter != NULL) { m_pHighFilter->set(1.0); } if (m_pLowFilterKill != NULL) { m_pLowFilterKill->set(0.0); } if (m_pMidFilterKill != NULL) { m_pMidFilterKill->set(0.0); } if (m_pHighFilterKill != NULL) { m_pHighFilterKill->set(0.0); } m_pPreGain->set(1.0); } int reset = m_pConfig->getValueString(ConfigKey( "[Controls]", "SpeedAutoReset"), QString("%1").arg(RESET_PITCH)).toInt(); switch (reset) { case RESET_PITCH_AND_SPEED: // Note: speed may affect pitch if (m_pSpeed != NULL) { m_pSpeed->set(0.0); } // Fallthrough intended case RESET_PITCH: if (m_pPitchAdjust != NULL) { m_pPitchAdjust->set(0.0); } } emit(newTrackLoaded(m_pLoadedTrack)); }
void GLWaveformRendererSimpleSignal::draw(QPainter* painter, QPaintEvent* /*event*/) { TrackPointer pTrack = m_waveformRenderer->getTrackInfo(); if (!pTrack) { return; } ConstWaveformPointer waveform = pTrack->getWaveform(); if (waveform.isNull()) { return; } const int dataSize = waveform->getDataSize(); if (dataSize <= 1) { return; } const WaveformData* data = waveform->data(); if (data == NULL) { return; } double firstVisualIndex = m_waveformRenderer->getFirstDisplayedPosition() * dataSize; double lastVisualIndex = m_waveformRenderer->getLastDisplayedPosition() * dataSize; const int firstIndex = int(firstVisualIndex+0.5); firstVisualIndex = firstIndex - firstIndex%2; const int lastIndex = int(lastVisualIndex+0.5); lastVisualIndex = lastIndex + lastIndex%2; // Reset device for native painting painter->beginNativePainting(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); float allGain(1.0); getGains(&allGain, NULL, NULL, NULL); float maxAll[2]; if (m_alignment == Qt::AlignCenter) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(firstVisualIndex, lastVisualIndex, -255.0, 255.0, -10.0, 10.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glScalef(1.f, allGain, 1.f); glLineWidth(1.0); glDisable(GL_LINE_SMOOTH); //draw reference line glBegin(GL_LINES); { glColor4f(m_axesColor_r, m_axesColor_g, m_axesColor_b, m_axesColor_a); glVertex2f(firstVisualIndex,0); glVertex2f(lastVisualIndex,0); } glEnd(); glLineWidth(1.1); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINES); { for (int visualIndex = firstVisualIndex; visualIndex < lastVisualIndex; visualIndex += 2) { if (visualIndex < 0) continue; if (visualIndex > dataSize - 1) break; maxAll[0] = (float)data[visualIndex].filtered.all; maxAll[1] = (float)data[visualIndex+1].filtered.all; glColor4f(m_signalColor_r, m_signalColor_g, m_signalColor_b, 0.9); glVertex2f(visualIndex,maxAll[0]); glVertex2f(visualIndex,-1.f*maxAll[1]); } } glEnd(); } else { //top || bottom glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); if (m_alignment == Qt::AlignBottom) glOrtho(firstVisualIndex, lastVisualIndex, 0.0, 255.0, -10.0, 10.0); else glOrtho(firstVisualIndex, lastVisualIndex, 255.0, 0.0, -10.0, 10.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glScalef(1.f, allGain, 1.f); glLineWidth(1.1); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINES); { for (int visualIndex = firstVisualIndex; visualIndex < lastVisualIndex; visualIndex += 2) { if (visualIndex < 0) continue; if (visualIndex > dataSize - 1) break; maxAll[0] = (float)data[visualIndex].filtered.all; maxAll[1] = (float)data[visualIndex+1].filtered.all; glColor4f(m_signalColor_r, m_signalColor_g, m_signalColor_b, 0.8); glVertex2f(float(visualIndex),0.f); glVertex2f(float(visualIndex),math_max(maxAll[0],maxAll[1])); } } glEnd(); } glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); painter->endNativePainting(); }
void SamplerBank::slotSaveSamplerBank(double v) { if (v == 0.0 || m_pPlayerManager == NULL) { return; } QString filefilter = tr("Mixxx Sampler Banks (*.xml)"); QString samplerBankPath = QFileDialog::getSaveFileName( NULL, tr("Save Sampler Bank"), QString(), tr("Mixxx Sampler Banks (*.xml)"), &filefilter); if (samplerBankPath.isNull() || samplerBankPath.isEmpty()) { return; } // Manually add extension due to bug in QFileDialog // via https://bugreports.qt-project.org/browse/QTBUG-27186 // Can be removed after switch to Qt5 QFileInfo fileName(samplerBankPath); if (fileName.suffix().isEmpty()) { QString ext = filefilter.section(".",1,1); ext.chop(1); samplerBankPath.append(".").append(ext); } // The user has picked a new directory via a file dialog. This means the // system sandboxer (if we are sandboxed) has granted us permission to this // folder. We don't need access to this file on a regular basis so we do not // register a security bookmark. QFile file(samplerBankPath); if (!file.open(QIODevice::WriteOnly)) { QMessageBox::warning(NULL, tr("Error Saving Sampler Bank"), tr("Could not write the sampler bank to '%1'.") .arg(samplerBankPath)); return; } QDomDocument doc("SamplerBank"); QDomElement root = doc.createElement("samplerbank"); doc.appendChild(root); for (unsigned int i = 0; i < m_pPlayerManager->numSamplers(); ++i) { Sampler* pSampler = m_pPlayerManager->getSampler(i + 1); if (pSampler == NULL) { continue; } QDomElement samplerNode = doc.createElement(QString("sampler")); samplerNode.setAttribute("group", pSampler->getGroup()); TrackPointer pTrack = pSampler->getLoadedTrack(); if (pTrack) { QString samplerLocation = pTrack->getLocation(); samplerNode.setAttribute("location", samplerLocation); } root.appendChild(samplerNode); } QString docStr = doc.toString(); file.write(docStr.toUtf8().constData()); file.close(); }
void GLWaveformRendererRGB::draw(QPainter* painter, QPaintEvent* /*event*/) { TrackPointer pTrack = m_waveformRenderer->getTrackInfo(); if (!pTrack) { return; } const Waveform* waveform = pTrack->getWaveform(); if (waveform == NULL) { return; } const int dataSize = waveform->getDataSize(); if (dataSize <= 1) { return; } const WaveformData* data = waveform->data(); if (data == NULL) { return; } double firstVisualIndex = m_waveformRenderer->getFirstDisplayedPosition() * dataSize; double lastVisualIndex = m_waveformRenderer->getLastDisplayedPosition() * dataSize; const int firstIndex = int(firstVisualIndex + 0.5); firstVisualIndex = firstIndex - firstIndex % 2; const int lastIndex = int(lastVisualIndex + 0.5); lastVisualIndex = lastIndex + lastIndex % 2; // Reset device for native painting painter->beginNativePainting(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Per-band gain from the EQ knobs. float lowGain(1.0), midGain(1.0), highGain(1.0); if (m_pLowFilterControlObject && m_pMidFilterControlObject && m_pHighFilterControlObject) { lowGain = m_pLowFilterControlObject->get(); midGain = m_pMidFilterControlObject->get(); highGain = m_pHighFilterControlObject->get(); } WaveformWidgetFactory* factory = WaveformWidgetFactory::instance(); const double visualGain = factory->getVisualGain(::WaveformWidgetFactory::All); lowGain *= factory->getVisualGain(WaveformWidgetFactory::Low); midGain *= factory->getVisualGain(WaveformWidgetFactory::Mid); highGain *= factory->getVisualGain(WaveformWidgetFactory::High); if (m_alignment == Qt::AlignCenter) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(firstVisualIndex, lastVisualIndex, -255.0, 255.0, -10.0, 10.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glScalef(1.0f, visualGain * m_waveformRenderer->getGain(), 1.0f); glLineWidth(1.2); glDisable(GL_LINE_SMOOTH); // Draw reference line glBegin(GL_LINES); { glColor4f(m_axesColor_r, m_axesColor_g, m_axesColor_b, m_axesColor_a); glVertex2f(firstVisualIndex, 0); glVertex2f(lastVisualIndex, 0); } glEnd(); glLineWidth(1.2); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINES); { for( int visualIndex = firstVisualIndex; visualIndex < lastVisualIndex; visualIndex += 2) { if( visualIndex < 0) continue; if( visualIndex > dataSize - 1) break; float left_low = lowGain * (float) data[visualIndex].filtered.low; float left_mid = midGain * (float) data[visualIndex].filtered.mid; float left_high = highGain * (float) data[visualIndex].filtered.high; float left_all = sqrtf(left_low * left_low + left_mid * left_mid + left_high * left_high); float left_red = left_low * m_lowColor.red() + left_mid * m_midColor.red() + left_high * m_highColor.red(); float left_green = left_low * m_lowColor.green() + left_mid * m_midColor.green() + left_high * m_highColor.green(); float left_blue = left_low * m_lowColor.blue() + left_mid * m_midColor.blue() + left_high * m_highColor.blue(); float left_max = MAX3(left_red, left_green, left_blue); if (left_max > 0.0f) { // Prevent division by zero glColor4f(left_red / left_max, left_green / left_max, left_blue / left_max, 0.8f); glVertex2f(visualIndex, 0.0f); glVertex2f(visualIndex, left_all); } float right_low = lowGain * (float) data[visualIndex+1].filtered.low; float right_mid = midGain * (float) data[visualIndex+1].filtered.mid; float right_high = highGain * (float) data[visualIndex+1].filtered.high; float right_all = sqrtf(right_low * right_low + right_mid * right_mid + right_high * right_high); float right_red = right_low * m_lowColor.red() + right_mid * m_midColor.red() + right_high * m_highColor.red(); float right_green = right_low * m_lowColor.green() + right_mid * m_midColor.green() + right_high * m_highColor.green(); float right_blue = right_low * m_lowColor.blue() + right_mid * m_midColor.blue() + right_high * m_highColor.blue(); float right_max = MAX3(right_red, right_green, right_blue); if (right_max > 0.0f) { // Prevent division by zero glColor4f(right_red / right_max, right_green / right_max, right_blue / right_max, 0.8f); glVertex2f(visualIndex, 0.0f); glVertex2f(visualIndex, -1.0f * right_all); } } } glEnd(); } else { // top || bottom glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); if( m_alignment == Qt::AlignBottom) glOrtho(firstVisualIndex, lastVisualIndex, 0.0, 255.0, -10.0, 10.0); else glOrtho(firstVisualIndex, lastVisualIndex, 255.0, 0.0, -10.0, 10.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glScalef(1.0f, visualGain * m_waveformRenderer->getGain(), 1.0f); glLineWidth(1.2); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINES); { for( int visualIndex = firstVisualIndex; visualIndex < lastVisualIndex; visualIndex += 2) { if( visualIndex < 0) continue; if( visualIndex > dataSize - 1) break; float low = lowGain * (float) math_max(data[visualIndex].filtered.low, data[visualIndex+1].filtered.low); float mid = midGain * (float) math_max(data[visualIndex].filtered.mid, data[visualIndex+1].filtered.mid); float high = highGain * (float) math_max(data[visualIndex].filtered.high, data[visualIndex+1].filtered.high); float all = sqrtf(low * low + mid * mid + high * high); float red = low * m_lowColor.red() + mid * m_midColor.red() + high * m_highColor.red(); float green = low * m_lowColor.green() + mid * m_midColor.green() + high * m_highColor.green(); float blue = low * m_lowColor.blue() + mid * m_midColor.blue() + high * m_highColor.blue(); float max = MAX3(red, green, blue); if (max > 0.0f) { // Prevent division by zero glColor4f(red / max, green / max, blue / max, 0.9f); glVertex2f(float(visualIndex), 0.0f); glVertex2f(float(visualIndex), all); } } } glEnd(); } glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); painter->endNativePainting(); }
void VinylControlControl::slotControlVinylSeek(double fractionalPos) { // Prevent NaN's from sneaking into the engine. if (isnan(fractionalPos)) { return; } // Do nothing if no track is loaded. TrackPointer pTrack = m_pTrack; if (!pTrack) { return; } double total_samples = getSampleOfTrack().total; double new_playpos = round(fractionalPos * total_samples); if (m_pControlVinylEnabled->get() > 0.0 && m_pControlVinylMode->get() == MIXXX_VCMODE_RELATIVE) { int cuemode = (int)m_pControlVinylCueing->get(); //if in preroll, always seek if (new_playpos < 0) { seekExact(new_playpos); return; } switch (cuemode) { case MIXXX_RELATIVE_CUE_OFF: return; // If off, do nothing. case MIXXX_RELATIVE_CUE_ONECUE: //if onecue, just seek to the regular cue seekExact(pTrack->getCuePoint().getPosition()); return; case MIXXX_RELATIVE_CUE_HOTCUE: // Continue processing in this function. break; default: qWarning() << "Invalid vinyl cue setting"; return; } double shortest_distance = 0; int nearest_playpos = -1; const QList<CuePointer> cuePoints(pTrack->getCuePoints()); QListIterator<CuePointer> it(cuePoints); while (it.hasNext()) { CuePointer pCue(it.next()); if (pCue->getType() != Cue::CUE || pCue->getHotCue() == -1) { continue; } int cue_position = pCue->getPosition(); // pick cues closest to new_playpos if ((nearest_playpos == -1) || (fabs(new_playpos - cue_position) < shortest_distance)) { nearest_playpos = cue_position; shortest_distance = fabs(new_playpos - cue_position); } } if (nearest_playpos == -1) { if (new_playpos >= 0) { //never found an appropriate cue, so don't seek? return; } //if negative, allow a seek by falling down to the bottom } else { m_bSeekRequested = true; seekExact(nearest_playpos); m_bSeekRequested = false; return; } } // Just seek where it wanted to originally. m_bSeekRequested = true; seekExact(new_playpos); m_bSeekRequested = false; }
void AnalyserQueue::run() { unsigned static id = 0; //the id of this thread, for debugging purposes QThread::currentThread()->setObjectName(QString("AnalyserQueue %1").arg(++id)); // If there are no analyzers, don't waste time running. if (m_aq.size() == 0) return; m_progressInfo.current_track = TrackPointer(); m_progressInfo.track_progress = 0; m_progressInfo.queue_size = 0; m_progressInfo.sema.release(); // Initalise with one while (!m_exit) { TrackPointer nextTrack = dequeueNextBlocking(); // It's important to check for m_exit here in case we decided to exit // while blocking for a new track. if (m_exit) return; // If the track is NULL, try to get the next one. // Could happen if the track was queued but then deleted. // Or if dequeueNextBlocking is unblocked by exit == true if (!nextTrack) { m_qm.lock(); m_queue_size = m_tioq.size(); m_qm.unlock(); if (m_queue_size == 0) { emit(queueEmpty()); // emit asynchrony for no deadlock } continue; } Trace trace("AnalyserQueue analyzing track"); // Get the audio SoundSourceProxy soundSourceProxy(nextTrack); Mixxx::SoundSourcePointer pSoundSource(soundSourceProxy.open()); if (pSoundSource.isNull()) { qWarning() << "Failed to open file for analyzing:" << nextTrack->getLocation(); continue; } int iNumSamples = pSoundSource->length(); int iSampleRate = pSoundSource->getSampleRate(); if (iNumSamples == 0 || iSampleRate == 0) { qWarning() << "Skipping invalid file:" << nextTrack->getLocation(); continue; } QListIterator<Analyser*> it(m_aq); bool processTrack = false; while (it.hasNext()) { // Make sure not to short-circuit initialise(...) if (it.next()->initialise(nextTrack, iSampleRate, iNumSamples)) { processTrack = true; } } m_qm.lock(); m_queue_size = m_tioq.size(); m_qm.unlock(); if (processTrack) { emitUpdateProgress(nextTrack, 0); bool completed = doAnalysis(nextTrack, pSoundSource); if (!completed) { //This track was cancelled QListIterator<Analyser*> itf(m_aq); while (itf.hasNext()) { itf.next()->cleanup(nextTrack); } queueAnalyseTrack(nextTrack); emitUpdateProgress(nextTrack, 0); } else { // 100% - FINALIZE_PERCENT finished emitUpdateProgress(nextTrack, 1000 - FINALIZE_PERCENT); // This takes around 3 sec on a Atom Netbook QListIterator<Analyser*> itf(m_aq); while (itf.hasNext()) { itf.next()->finalise(nextTrack); } emit(trackDone(nextTrack)); emitUpdateProgress(nextTrack, 1000); // 100% } } else { emitUpdateProgress(nextTrack, 1000); // 100% qDebug() << "Skipping track analysis because no analyzer initialized."; } m_qm.lock(); m_queue_size = m_tioq.size(); m_qm.unlock(); if (m_queue_size == 0) { emit(queueEmpty()); // emit asynchrony for no deadlock } } emit(queueEmpty()); // emit in case of exit; }
TEST_F(CueControlTest, OutroCue_SetStartEnd_ClearStartEnd) { TrackPointer pTrack = createAndLoadFakeTrack(); // Set outro start cue setCurrentSample(750.0); m_pOutroStartSet->slotSet(1); m_pOutroStartSet->slotSet(0); EXPECT_DOUBLE_EQ(750.0, m_pOutroStartPosition->get()); EXPECT_TRUE(m_pOutroStartEnabled->toBool()); EXPECT_DOUBLE_EQ(-1.0, m_pOutroEndPosition->get()); EXPECT_FALSE(m_pOutroEndEnabled->toBool()); CuePointer pCue = pTrack->findCueByType(Cue::OUTRO); EXPECT_NE(nullptr, pCue); if (pCue != nullptr) { EXPECT_DOUBLE_EQ(750.0, pCue->getPosition()); EXPECT_DOUBLE_EQ(0.0, pCue->getLength()); EXPECT_DOUBLE_EQ(Cue::MANUAL, pCue->getSource()); } // Set outro end cue setCurrentSample(1000.0); m_pOutroEndSet->slotSet(1); m_pOutroEndSet->slotSet(0); EXPECT_DOUBLE_EQ(750.0, m_pOutroStartPosition->get()); EXPECT_TRUE(m_pOutroStartEnabled->toBool()); EXPECT_DOUBLE_EQ(1000.0, m_pOutroEndPosition->get()); EXPECT_TRUE(m_pOutroEndEnabled->toBool()); pCue = pTrack->findCueByType(Cue::OUTRO); EXPECT_NE(nullptr, pCue); if (pCue != nullptr) { EXPECT_DOUBLE_EQ(750.0, pCue->getPosition()); EXPECT_DOUBLE_EQ(250.0, pCue->getLength()); EXPECT_DOUBLE_EQ(Cue::MANUAL, pCue->getSource()); } // Clear outro start cue m_pOutroStartClear->slotSet(1); m_pOutroStartClear->slotSet(0); EXPECT_DOUBLE_EQ(-1.0, m_pOutroStartPosition->get()); EXPECT_FALSE(m_pOutroStartEnabled->toBool()); EXPECT_DOUBLE_EQ(1000.0, m_pOutroEndPosition->get()); EXPECT_TRUE(m_pOutroEndEnabled->toBool()); pCue = pTrack->findCueByType(Cue::OUTRO); EXPECT_NE(nullptr, pCue); if (pCue != nullptr) { EXPECT_DOUBLE_EQ(-1.0, pCue->getPosition()); EXPECT_DOUBLE_EQ(1000.0, pCue->getLength()); EXPECT_DOUBLE_EQ(Cue::MANUAL, pCue->getSource()); } // Clear outro end cue m_pOutroEndClear->slotSet(1); m_pOutroEndClear->slotSet(0); EXPECT_DOUBLE_EQ(-1.0, m_pOutroStartPosition->get()); EXPECT_FALSE(m_pOutroStartEnabled->toBool()); EXPECT_DOUBLE_EQ(-1.0, m_pOutroEndPosition->get()); EXPECT_FALSE(m_pOutroEndEnabled->toBool()); EXPECT_EQ(nullptr, pTrack->findCueByType(Cue::OUTRO)); }
void CueControl::trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack) { Q_UNUSED(pOldTrack); QMutexLocker lock(&m_mutex); if (m_pLoadedTrack) { disconnect(m_pLoadedTrack.get(), 0, this, 0); for (int i = 0; i < m_iNumHotCues; ++i) { detachCue(i); } // Store the cue point in a load cue. double cuePoint = m_pCuePoint->get(); if (cuePoint != -1 && cuePoint != 0.0) { CuePointer loadCue; const QList<CuePointer> cuePoints(m_pLoadedTrack->getCuePoints()); QListIterator<CuePointer> it(cuePoints); while (it.hasNext()) { CuePointer pCue(it.next()); if (pCue->getType() == Cue::LOAD) { loadCue = pCue; break; } } if (!loadCue) { loadCue = m_pLoadedTrack->addCue(); loadCue->setType(Cue::LOAD); loadCue->setLength(0); } loadCue->setPosition(cuePoint); } m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); m_pCuePoint->set(-1.0); m_pLoadedTrack.reset(); } if (!pNewTrack) { return; } m_pLoadedTrack = pNewTrack; connect(pNewTrack.get(), SIGNAL(cuesUpdated()), this, SLOT(trackCuesUpdated()), Qt::DirectConnection); CuePointer loadCue; const QList<CuePointer> cuePoints(pNewTrack->getCuePoints()); QListIterator<CuePointer> it(cuePoints); while (it.hasNext()) { CuePointer pCue(it.next()); if (pCue->getType() == Cue::LOAD) { loadCue = pCue; } else if (pCue->getType() != Cue::CUE) { continue; } int hotcue = pCue->getHotCue(); if (hotcue != -1) attachCue(pCue, hotcue); } double loadCuePoint = 0.0; // If cue recall is ON in the prefs, then we're supposed to seek to the cue // point on song load. Note that [Controls],cueRecall == 0 corresponds to "ON", not OFF. bool cueRecall = (getConfig()->getValueString( ConfigKey("[Controls]","CueRecall"), "0").toInt() == 0); if (loadCue != NULL) { m_pCuePoint->set(loadCue->getPosition()); if (cueRecall) { loadCuePoint = loadCue->getPosition(); } } else { // If no cue point is stored, set one at track start m_pCuePoint->set(0.0); } // Need to unlock before emitting any signals to prevent deadlock. lock.unlock(); // If cueRecall is on, seek to it even if we didn't find a cue value (we'll // seek to 0. if (cueRecall) { seekExact(loadCuePoint); } else if (!(m_pVinylControlEnabled->get() && m_pVinylControlMode->get() == MIXXX_VCMODE_ABSOLUTE)) { // If cuerecall is off, seek to zero unless // vinylcontrol is on and set to absolute. This allows users to // load tracks and have the needle-drop be maintained. seekExact(0.0); } }