![]() |
Mixxx
|
00001 // readaheadmanager.cpp 00002 // Created 8/2/2009 by RJ Ryan (rryan@mit.edu) 00003 00004 #include <QMutexLocker> 00005 00006 #include "engine/readaheadmanager.h" 00007 00008 #include "mathstuff.h" 00009 #include "engine/enginecontrol.h" 00010 #include "cachingreader.h" 00011 00012 00013 ReadAheadManager::ReadAheadManager(CachingReader* pReader) : 00014 m_iCurrentPosition(0), 00015 m_pReader(pReader) { 00016 } 00017 00018 ReadAheadManager::~ReadAheadManager() { 00019 } 00020 00021 int ReadAheadManager::getNextSamples(double dRate, CSAMPLE* buffer, 00022 int requested_samples) { 00023 Q_ASSERT(even(requested_samples)); 00024 00025 bool in_reverse = dRate < 0; 00026 int start_sample = m_iCurrentPosition; 00027 //qDebug() << "start" << start_sample << requested_samples; 00028 int samples_needed = requested_samples; 00029 CSAMPLE* base_buffer = buffer; 00030 00031 // A loop will only limit the amount we can read in one shot. 00032 00033 QPair<int, double> next_loop; 00034 next_loop.first = 0; 00035 next_loop.second = m_sEngineControls[0]->nextTrigger(dRate, 00036 m_iCurrentPosition, 00037 0, 0); 00038 00039 if (next_loop.second != kNoTrigger) { 00040 int samples_available; 00041 if (in_reverse) { 00042 samples_available = m_iCurrentPosition - next_loop.second; 00043 } else { 00044 samples_available = next_loop.second - m_iCurrentPosition; 00045 } 00046 samples_needed = math_max(0, math_min(samples_needed, 00047 samples_available)); 00048 } 00049 00050 if (in_reverse) { 00051 start_sample = m_iCurrentPosition - samples_needed; 00052 /*if (start_sample < 0) { 00053 samples_needed = math_max(0, samples_needed + start_sample); 00054 start_sample = 0; 00055 }*/ 00056 } 00057 00058 // Sanity checks 00059 //Q_ASSERT(start_sample >= 0); 00060 Q_ASSERT(samples_needed >= 0); 00061 00062 int samples_read = m_pReader->read(start_sample, samples_needed, 00063 base_buffer); 00064 00065 if (samples_read != samples_needed) 00066 qDebug() << "didn't get what we wanted" << samples_read << samples_needed; 00067 00068 // Increment or decrement current read-ahead position 00069 if (in_reverse) { 00070 addReadLogEntry(m_iCurrentPosition, m_iCurrentPosition - samples_read); 00071 m_iCurrentPosition -= samples_read; 00072 } else { 00073 addReadLogEntry(m_iCurrentPosition, m_iCurrentPosition + samples_read); 00074 m_iCurrentPosition += samples_read; 00075 } 00076 00077 // Activate on this trigger if necessary 00078 if (next_loop.second != kNoTrigger) { 00079 double loop_trigger = next_loop.second; 00080 double loop_target = m_sEngineControls[next_loop.first]-> 00081 getTrigger(dRate, 00082 m_iCurrentPosition, 00083 0, 0); 00084 00085 if (loop_target != kNoTrigger && 00086 ((in_reverse && m_iCurrentPosition <= loop_trigger) || 00087 (!in_reverse && m_iCurrentPosition >= loop_trigger))) { 00088 m_iCurrentPosition = loop_target; 00089 } 00090 } 00091 00092 // Reverse the samples in-place 00093 if (in_reverse) { 00094 // TODO(rryan) pull this into MixxxUtil or something 00095 CSAMPLE temp1, temp2; 00096 for (int j = 0; j < samples_read/2; j += 2) { 00097 const int endpos = samples_read-1-j-1; 00098 temp1 = base_buffer[j]; 00099 temp2 = base_buffer[j+1]; 00100 base_buffer[j] = base_buffer[endpos]; 00101 base_buffer[j+1] = base_buffer[endpos+1]; 00102 base_buffer[endpos] = temp1; 00103 base_buffer[endpos+1] = temp2; 00104 } 00105 } 00106 00107 //qDebug() << "read" << m_iCurrentPosition << samples_read; 00108 return samples_read; 00109 } 00110 00111 void ReadAheadManager::addEngineControl(EngineControl* pControl) { 00112 Q_ASSERT(pControl); 00113 m_sEngineControls.append(pControl); 00114 } 00115 00116 void ReadAheadManager::setNewPlaypos(int iNewPlaypos) { 00117 QMutexLocker locker(&m_mutex); 00118 m_iCurrentPosition = iNewPlaypos; 00119 m_readAheadLog.clear(); 00120 } 00121 00122 void ReadAheadManager::notifySeek(int iSeekPosition) { 00123 QMutexLocker locker(&m_mutex); 00124 m_iCurrentPosition = iSeekPosition; 00125 m_readAheadLog.clear(); 00126 00127 // TODO(XXX) notifySeek on the engine controls. EngineBuffer currently does 00128 // a fine job of this so it isn't really necessary but eventually I think 00129 // RAMAN should do this job. rryan 11/2011 00130 00131 // foreach (EngineControl* pControl, m_sEngineControls) { 00132 // pControl->notifySeek(iSeekPosition); 00133 // } 00134 } 00135 00136 void ReadAheadManager::hintReader(double dRate, QList<Hint>& hintList, 00137 int iSamplesPerBuffer) { 00138 bool in_reverse = dRate < 0; 00139 Hint current_position; 00140 00141 // SoundTouch can read up to 2 chunks ahead. Always keep 2 chunks ahead in 00142 // cache. 00143 int length_to_cache = 2*CachingReader::kSamplesPerChunk; 00144 00145 current_position.length = length_to_cache; 00146 current_position.sample = in_reverse ? 00147 m_iCurrentPosition - length_to_cache : 00148 m_iCurrentPosition; 00149 00150 // If we are trying to cache before the start of the track, 00151 // Then we don't need to cache because it's all zeros! 00152 if (current_position.sample < 0 && 00153 current_position.sample + current_position.length < 0) 00154 return; 00155 00156 // top priority, we need to read this data immediately 00157 current_position.priority = 1; 00158 hintList.append(current_position); 00159 } 00160 00161 void ReadAheadManager::addReadLogEntry(double virtualPlaypositionStart, 00162 double virtualPlaypositionEndNonInclusive) { 00163 QMutexLocker locker(&m_mutex); 00164 ReadLogEntry newEntry(virtualPlaypositionStart, 00165 virtualPlaypositionEndNonInclusive); 00166 if (m_readAheadLog.size() > 0) { 00167 ReadLogEntry& last = m_readAheadLog.last(); 00168 if (last.merge(newEntry)) { 00169 return; 00170 } 00171 } 00172 m_readAheadLog.append(newEntry); 00173 } 00174 00175 int ReadAheadManager::getEffectiveVirtualPlaypositionFromLog(double currentVirtualPlayposition, 00176 double numConsumedSamples) { 00177 if (numConsumedSamples == 0) { 00178 return currentVirtualPlayposition; 00179 } 00180 00181 QMutexLocker locker(&m_mutex); 00182 if (m_readAheadLog.size() == 0) { 00183 // No log entries to read from. 00184 qDebug() << this << "No read ahead log entries to read from. Case not currently handled."; 00185 // TODO(rryan) log through a stats pipe eventually 00186 return currentVirtualPlayposition; 00187 } 00188 00189 double virtualPlayposition = 0; 00190 bool shouldNotifySeek = false; 00191 bool direction = true; 00192 while (m_readAheadLog.size() > 0 && numConsumedSamples > 0) { 00193 ReadLogEntry& entry = m_readAheadLog.first(); 00194 direction = entry.direction(); 00195 00196 // Notify EngineControls that we have taken a seek. 00197 if (shouldNotifySeek) { 00198 foreach (EngineControl* pControl, m_sEngineControls) { 00199 pControl->notifySeek(entry.virtualPlaypositionStart); 00200 } 00201 } 00202 00203 double consumed = entry.consume(numConsumedSamples); 00204 numConsumedSamples -= consumed; 00205 00206 // Advance our idea of the current virtual playposition to this 00207 // ReadLogEntry's start position. 00208 virtualPlayposition = entry.virtualPlaypositionStart; 00209 00210 if (entry.length() == 0) { 00211 // This entry is empty now. 00212 m_readAheadLog.removeFirst(); 00213 } 00214 shouldNotifySeek = true; 00215 } 00216 int result = 0; 00217 if (direction) { 00218 result = static_cast<int>(floor(virtualPlayposition)); 00219 if (!even(result)) { 00220 result--; 00221 } 00222 } else { 00223 result = static_cast<int>(ceil(virtualPlayposition)); 00224 if (!even(result)) { 00225 result++; 00226 } 00227 } 00228 return result; 00229 }