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);
//.........这里部分代码省略.........
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 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 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 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;
//.........这里部分代码省略.........
请发表评论