Merge lp:~ywwg/mixxx/features_xwax2 into lp:mixxx/1.10

Proposed by Owen Williams on 2011-07-31
Status: Merged
Merged at revision: 2863
Proposed branch: lp:~ywwg/mixxx/features_xwax2
Merge into: lp:mixxx/1.10
Diff against target: 3843 lines (+1680/-472) (has conflicts)
57 files modified
mixxx/build/depends.py (+3/-0)
mixxx/lib/xwax/timecoder.c (+1/-1)
mixxx/lib/xwax/timecoder_win32.cpp (+1/-1)
mixxx/src/cachingreader.cpp (+163/-145)
mixxx/src/cachingreader.h (+79/-74)
mixxx/src/dlgprefnovinyldlg.ui (+23/-0)
mixxx/src/dlgprefvinyl.cpp (+3/-0)
mixxx/src/dlgprefvinyldlg.ui (+7/-0)
mixxx/src/engine/enginebuffer.cpp (+5/-9)
mixxx/src/engine/enginebufferscalelinear.cpp (+0/-10)
mixxx/src/engine/enginebufferscalest.h (+5/-5)
mixxx/src/engine/enginedeck.cpp (+1/-2)
mixxx/src/engine/enginemaster.cpp (+11/-2)
mixxx/src/engine/enginemaster.h (+2/-0)
mixxx/src/engine/enginevinylsoundemu.cpp (+37/-25)
mixxx/src/engine/enginevinylsoundemu.h (+6/-1)
mixxx/src/engine/engineworkerscheduler.cpp (+25/-11)
mixxx/src/engine/engineworkerscheduler.h (+21/-6)
mixxx/src/engine/readaheadmanager.cpp (+12/-7)
mixxx/src/engine/readaheadmanager.h (+2/-1)
mixxx/src/engine/syncworker.cpp (+25/-0)
mixxx/src/engine/syncworker.h (+20/-0)
mixxx/src/engine/vinylcontrolcontrol.cpp (+6/-0)
mixxx/src/engine/vinylcontrolcontrol.h (+2/-0)
mixxx/src/library/basesqltablemodel.cpp (+85/-0)
mixxx/src/library/basetrackcache.cpp (+15/-2)
mixxx/src/library/dao/trackdao.cpp (+3/-1)
mixxx/src/library/itunes/itunesfeature.cpp (+45/-20)
mixxx/src/library/preparelibrarytablemodel.cpp (+5/-1)
mixxx/src/library/rhythmbox/rhythmboxfeature.cpp (+1/-1)
mixxx/src/library/traktor/traktorfeature.cpp (+1/-1)
mixxx/src/mixxx.cpp (+10/-35)
mixxx/src/skin/legacyskinparser.cpp (+4/-2)
mixxx/src/skin/legacyskinparser.h (+3/-1)
mixxx/src/skin/skinloader.cpp (+7/-1)
mixxx/src/skin/skinloader.h (+2/-1)
mixxx/src/soundmanager.cpp (+1/-9)
mixxx/src/soundmanager.h (+0/-2)
mixxx/src/track/beatfactory.cpp (+4/-5)
mixxx/src/trackinfoobject.cpp (+11/-1)
mixxx/src/trackinfoobject.h (+4/-0)
mixxx/src/util/fifo.h (+44/-0)
mixxx/src/util/pa_memorybarrier.h (+128/-0)
mixxx/src/util/pa_ringbuffer.c (+238/-0)
mixxx/src/util/pa_ringbuffer.h (+233/-0)
mixxx/src/vinylcontrol/vinylcontrol.cpp (+15/-2)
mixxx/src/vinylcontrol/vinylcontrol.h (+6/-1)
mixxx/src/vinylcontrol/vinylcontrolmanager.cpp (+17/-6)
mixxx/src/vinylcontrol/vinylcontrolmanager.h (+1/-0)
mixxx/src/vinylcontrol/vinylcontrolsignalwidget.cpp (+1/-1)
mixxx/src/vinylcontrol/vinylcontrolxwax.cpp (+118/-61)
mixxx/src/vinylcontrol/vinylcontrolxwax.h (+2/-1)
mixxx/src/waveform/waveformrenderer.cpp (+24/-1)
mixxx/src/waveform/waveformrenderer.h (+2/-1)
mixxx/src/widget/wspinny.cpp (+160/-10)
mixxx/src/widget/wspinny.h (+25/-1)
mixxx/src/widget/wtracktableview.cpp (+5/-4)
Text conflict in mixxx/src/library/basesqltablemodel.cpp
Text conflict in mixxx/src/library/basetrackcache.cpp
Text conflict in mixxx/src/library/itunes/itunesfeature.cpp
Text conflict in mixxx/src/library/preparelibrarytablemodel.cpp
Text conflict in mixxx/src/skin/skinloader.cpp
To merge this branch: bzr merge lp:~ywwg/mixxx/features_xwax2
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan 2011-07-31 Approve on 2011-10-04
Review via email: mp+69921@code.launchpad.net

Description of the change

I've continued working in features_xwax2 to make some fixes and updates to the vinyl control code. It would be nice if some of these fixes could go into trunk, other new features can probably wait if it breaks freeze too badly.

New Features:
* Show signal quality inside WSpinny
* More pleasant waveform stretching

Fixes:
* fix vinylsoundemu and take equivalent code out of EBSL
* when loading a track, update track duration with correct value
* keep vinyl control enabled when changing vinyl control preferences
* Absolute mode fixes (better for scratching)

To post a comment you must log in.
lp:~ywwg/mixxx/features_xwax2 updated on 2011-07-31
2664. By Owen Williams on 2011-07-31

Merge from lp:mixxx

RJ Skerry-Ryan (rryan) wrote :

Looking good Owen -- I think this is fine to go in 1.10. I'm going on your say-so that it works since I can't test it myself, so this is just a review of the code assuming it all works :P

enginevinylsoundemu.cpp

* Just to make sure fabs(curRate) is not repeated over and over, could you assign it to a temporary variable once per loop?

* Using rand() in the callback isn't ok because technically it can block if the system has run out of entropy (at least if it's reading from /dev/random). Even if it doesn't block, random number generation aint cheap. Can you pre-generate a large buffer of random numbers in the constructor and just use that (with a class-level stored index into the buffer so you aren't adding the same section each time).

enginebuffer.cpp

* Duration is read from metadata and shouldn't be considered accurate. Also I don't think we should correct it if it's wrong from the engine. Do you rely on duration anywhere from within VC? Can we remove this line?

vinylcontrolsignalwidget.cpp

* There's a constant in vinylcontrol.h MIXXX_VINYL_SCOPE_SIZE that seems to be this number. Can you use that instead?

wspinny.cpp

* in updateVinylControlEnabled() there is some non-ndeck code -- I know VC itself is 2-deck centric, but could you instead add a VinylControlManager::getVinylControlProxyForChannel(QString) method that returns either the proxy or NULL? That way WSpinny doesn't need an update when we eventually n-deck vinyl control.

review: Needs Fixing
Owen Williams (ywwg) wrote :

Just a couple of comments as I work on this:

> enginebuffer.cpp
>
> * Duration is read from metadata and shouldn't be considered accurate. Also I
> don't think we should correct it if it's wrong from the engine. Do you rely on
> duration anywhere from within VC? Can we remove this line?

Vinyl control needs an exact duration (sample-accurate) to calculate position and drift, and the UI timers (time remaining / track duration) also need better precision (there's a bug in launchpad for this). What is the point of the duration member if it's several seconds off? Why not fix it? Otherwise I need to add a new object called "exact_duration" or "calculated_duration" or something.

>
> vinylcontrolsignalwidget.cpp
>
> * There's a constant in vinylcontrol.h MIXXX_VINYL_SCOPE_SIZE that seems to be
> this number. Can you use that instead?

the scope size is the size of the bitmap created by xwax. This bitmap can then be scaled to whatever size the scope widget actually is. I'll set it to default to the scope size, but they don't need to be the same.

lp:~ywwg/mixxx/features_xwax2 updated on 2011-10-01
2665. By Owen Williams on 2011-09-16

Calculate fabs value once, not three times

2666. By Owen Williams on 2011-09-17

Replace explicit number with #defined value

2667. By Owen Williams on 2011-09-17

Optimizing vinyl sound emu:
* precompute random values for dithering
* don't repeatedly calculate absolute value

2668. By Owen Williams on 2011-09-17

Also randomize position in noise buffer so right and left aren't aligned

2669. By Owen Williams on 2011-09-17

Always record pitch for steadypitch, not just when we have position

2670. By Owen Williams on 2011-09-17

Retweak pitch smoothing tweak. Make sure spurious values aren't in the ringbuffer

2671. By Owen Williams on 2011-09-25

Don't use static variables

2672. By Owen Williams on 2011-09-26

Merge from lp:mixxx

2673. By Owen Williams on 2011-09-28

include genre in search results

2674. By Owen Williams on 2011-09-28

merge from lp:mixxx

2675. By Owen Williams on 2011-09-30

Abstractify vinyl proxies more

2676. By Owen Williams on 2011-10-01

Don't change metadata-reported duration

Owen Williams (ywwg) wrote :

This branch is ready for reevaluation.

lp:~ywwg/mixxx/features_xwax2 updated on 2011-10-03
2677. By Owen Williams on 2011-10-01

merge from lp:mixxx

2678. By Owen Williams on 2011-10-03

Merge with lp:mixxx

RJ Skerry-Ryan (rryan) wrote :

Looks good to me. Merge away!

review: Approve
lp:~ywwg/mixxx/features_xwax2 updated on 2011-10-04
2679. By Owen Williams on 2011-10-04

merge from lp:mixxx

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/build/depends.py'
2--- mixxx/build/depends.py 2011-07-19 18:12:33 +0000
3+++ mixxx/build/depends.py 2011-10-04 14:01:27 +0000
4@@ -349,6 +349,7 @@
5
6 "engine/engineworker.cpp",
7 "engine/engineworkerscheduler.cpp",
8+ "engine/syncworker.cpp",
9 "engine/enginebuffer.cpp",
10 "engine/enginebufferscale.cpp",
11 "engine/enginebufferscaledummy.cpp",
12@@ -571,6 +572,8 @@
13
14 "segmentation.cpp",
15 "tapfilter.cpp",
16+
17+ "util/pa_ringbuffer.c",
18 ]
19
20 # Uic these guys (they're moc'd automatically after this) - Generates
21
22=== modified file 'mixxx/lib/xwax/timecoder.c'
23--- mixxx/lib/xwax/timecoder.c 2011-05-25 19:52:14 +0000
24+++ mixxx/lib/xwax/timecoder.c 2011-10-04 14:01:27 +0000
25@@ -519,7 +519,7 @@
26
27 if (r >= 0) {
28 //normalize position to milliseconds, not timecode steps -- Owen
29- r = r * 1000 / (tc->def->resolution * tc->speed);
30+ r = (float)r * (1000.0 / (tc->def->resolution * tc->speed));
31 if (when)
32 *when = tc->timecode_ticker * tc->dt;
33 return r;
34
35=== modified file 'mixxx/lib/xwax/timecoder_win32.cpp'
36--- mixxx/lib/xwax/timecoder_win32.cpp 2011-05-25 19:52:14 +0000
37+++ mixxx/lib/xwax/timecoder_win32.cpp 2011-10-04 14:01:27 +0000
38@@ -521,7 +521,7 @@
39
40 if (r >= 0) {
41 //normalize position to milliseconds, not timecode steps -- Owen
42- r = r * 1000 / (tc->def->resolution * tc->speed);
43+ r = (float)r * (1000.0 / (tc->def->resolution * tc->speed));
44 if (when)
45 *when = tc->timecode_ticker * tc->dt;
46 return r;
47
48=== modified file 'mixxx/src/cachingreader.cpp'
49--- mixxx/src/cachingreader.cpp 2011-04-16 19:57:04 +0000
50+++ mixxx/src/cachingreader.cpp 2011-10-04 14:01:27 +0000
51@@ -31,21 +31,20 @@
52 ConfigObject<ConfigValue>* _config) :
53 m_pGroup(_group),
54 m_pConfig(_config),
55- m_pCurrentTrack(),
56 m_pCurrentSoundSource(NULL),
57 m_iTrackSampleRate(0),
58 m_iTrackNumSamples(0),
59+ m_iTrackNumSamplesCallbackSafe(0),
60+ m_readerStatus(INVALID),
61 m_mruChunk(NULL),
62 m_lruChunk(NULL),
63 m_pRawMemoryBuffer(NULL),
64- m_iRawMemoryBufferLength(0),
65- m_bQuit(false) {
66+ m_chunkReadRequestFIFO(1024),
67+ m_readerStatusFIFO(1024) {
68 initialize();
69 }
70
71 CachingReader::~CachingReader() {
72-
73- m_readerMutex.lock();
74 m_freeChunks.clear();
75 m_allocatedChunks.clear();
76 m_lruChunk = m_mruChunk = NULL;
77@@ -54,9 +53,6 @@
78
79 delete [] m_pRawMemoryBuffer;
80 m_pRawMemoryBuffer = NULL;
81- m_iRawMemoryBufferLength = 0;
82-
83- m_readerMutex.unlock();
84 }
85
86 void CachingReader::initialize() {
87@@ -74,8 +70,8 @@
88
89 qDebug() << "CachingReader using" << memory_to_use << "bytes.";
90
91- m_iRawMemoryBufferLength = kSamplesPerChunk * total_chunks;
92- m_pRawMemoryBuffer = new CSAMPLE[m_iRawMemoryBufferLength];
93+ int rawMemoryBufferLength = kSamplesPerChunk * total_chunks;
94+ m_pRawMemoryBuffer = new CSAMPLE[rawMemoryBufferLength];
95
96 m_allocatedChunks.reserve(total_chunks);
97
98@@ -167,8 +163,13 @@
99 m_allocatedChunks.clear();
100 m_mruChunk = NULL;
101
102+ QSet<Chunk*> reserved = QSet<Chunk*>::fromList(m_chunksBeingRead.values());
103+
104 for (int i=0; i < m_chunks.size(); i++) {
105 Chunk* c = m_chunks[i];
106+ if (reserved.contains(c)) {
107+ continue;
108+ }
109 if (!m_freeChunks.contains(c)) {
110 c->chunk_number = -1;
111 c->length = 0;
112@@ -189,11 +190,12 @@
113 Chunk* chunk = allocateChunk();
114 if (chunk == NULL) {
115 Q_ASSERT(m_lruChunk);
116- //qDebug() << "Expiring LRU" << m_lruChunk->chunk_number;
117+ //qDebug() << "Expiring LRU" << m_lruChunk << m_lruChunk->chunk_number;
118 freeChunk(m_lruChunk);
119 chunk = allocateChunk();
120 Q_ASSERT(chunk);
121 }
122+ //qDebug() << "allocateChunkExpireLRU" << chunk;
123 return chunk;
124 }
125
126@@ -207,6 +209,7 @@
127 // Make sure we're all in agreement here.
128 Q_ASSERT(chunk_number == chunk->chunk_number);
129
130+
131 // If this is the LRU chunk then set the previous LRU to the new LRU
132 if (chunk == m_lruChunk && chunk->prev_lru != NULL) {
133 m_lruChunk = chunk->prev_lru;
134@@ -219,74 +222,48 @@
135 return chunk;
136 }
137
138-Chunk* CachingReader::getChunk(int chunk_number, bool* cache_miss) {
139- Chunk* chunk = lookupChunk(chunk_number);
140-
141- if (cache_miss != NULL)
142- *cache_miss = (chunk == NULL);
143-
144- // If it wasn't in the cache, read it from file.
145- if (chunk == NULL) {
146- //qDebug() << m_pGroup << "Cache miss on chunk " << chunk_number;
147- chunk = allocateChunkExpireLRU();
148- Q_ASSERT(chunk != NULL);
149-
150- if (!readChunkFromFile(chunk, chunk_number)) {
151- //qDebug() << m_pGroup << "Failed to read chunk " << chunk_number;
152- freeChunk(chunk);
153- return NULL;
154- } else {
155- Q_ASSERT(chunk_number == chunk->chunk_number);
156- m_allocatedChunks.insert(chunk_number, chunk);
157-
158- // Insert the chunk into the LRU list
159- m_mruChunk = insertIntoLRUList(chunk, m_mruChunk);
160-
161- // If this chunk has no next LRU then it is the LRU. This only
162- // happens if this is the first allocated chunk.
163- if (chunk->next_lru == NULL) {
164- m_lruChunk = chunk;
165- }
166-
167- }
168+void CachingReader::processChunkReadRequest(ChunkReadRequest* request,
169+ ReaderStatusUpdate* update) {
170+ int chunk_number = request->chunk->chunk_number;
171+ //qDebug() << "Processing ChunkReadRequest for" << chunk_number;
172+ update->status = CHUNK_READ_INVALID;
173+ update->chunk = request->chunk;
174+ update->chunk->length = 0;
175+
176+ if (m_pCurrentSoundSource == NULL || chunk_number < 0) {
177+ return;
178 }
179
180- return chunk;
181-}
182-
183-bool CachingReader::readChunkFromFile(Chunk* pChunk, int chunk_number) {
184-
185- if (m_pCurrentSoundSource == NULL || pChunk == NULL || chunk_number < 0)
186- return false;
187-
188 // Stereo samples
189 int sample_position = sampleForChunk(chunk_number);
190 int samples_remaining = m_iTrackNumSamples - sample_position;
191 int samples_to_read = math_min(kSamplesPerChunk, samples_remaining);
192
193 // Bogus chunk number
194- if (samples_to_read <= 0)
195- return false;
196+ if (samples_to_read <= 0) {
197+ update->status = CHUNK_READ_EOF;
198+ return;
199+ }
200
201 m_pCurrentSoundSource->seek(sample_position);
202 int samples_read = m_pCurrentSoundSource->read(samples_to_read,
203 m_pSample);
204
205- //If we've run out of music, the SoundSource can return 0 samples.
206- //Remember that SoundSourc->getLength() (which is m_iTrackNumSamples)
207- //can lie to us about the length of the song!
208- if (samples_read <= 0)
209- return false;
210+ // If we've run out of music, the SoundSource can return 0 samples.
211+ // Remember that SoundSourc->getLength() (which is m_iTrackNumSamples) can
212+ // lie to us about the length of the song!
213+ if (samples_read <= 0) {
214+ update->status = CHUNK_READ_EOF;
215+ return;
216+ }
217
218 // TODO(XXX) This loop can't be done with a memcpy, but could be done with
219 // SSE.
220- CSAMPLE* buffer = pChunk->data;
221+ CSAMPLE* buffer = request->chunk->data;
222+ //qDebug() << "Reading into " << buffer;
223 SampleUtil::convert(buffer, m_pSample, samples_read);
224-
225- pChunk->chunk_number = chunk_number;
226- pChunk->length = samples_read;
227-
228- return true;
229+ update->status = CHUNK_READ_SUCCESS;
230+ update->chunk->length = samples_read;
231 }
232
233 void CachingReader::newTrack(TrackPointer pTrack) {
234@@ -295,6 +272,65 @@
235 m_trackQueueMutex.unlock();
236 }
237
238+void CachingReader::process() {
239+ ReaderStatusUpdate status;
240+ while (m_readerStatusFIFO.read(&status, 1) == 1) {
241+ // qDebug() << "Got ReaderStatusUpdate:" << status.status
242+ // << (status.chunk ? status.chunk->chunk_number : -1);
243+ if (status.status == TRACK_NOT_LOADED) {
244+ m_readerStatus = status.status;
245+ } else if (status.status == TRACK_LOADED) {
246+ freeAllChunks();
247+ m_readerStatus = status.status;
248+ m_iTrackNumSamplesCallbackSafe = status.trackNumSamples;
249+ } else if (status.status == CHUNK_READ_SUCCESS) {
250+ Chunk* pChunk = status.chunk;
251+ Q_ASSERT(pChunk != NULL);
252+ Chunk* pChunk2 = m_chunksBeingRead.take(pChunk->chunk_number);
253+ if (pChunk2 != pChunk) {
254+ qDebug() << "Mismatch in requested chunk to read!";
255+ }
256+
257+ Chunk* pAlreadyExisting = lookupChunk(pChunk->chunk_number);
258+ // If this chunk is already in the cache, then we just freshened
259+ // it. Free this chunk.
260+ if (pAlreadyExisting != NULL) {
261+ qDebug() << "CHUNK" << pChunk->chunk_number << "ALREADY EXISTS!";
262+ freeChunk(pChunk);
263+ } else {
264+ //qDebug() << "Inserting chunk" << pChunk << pChunk->chunk_number;
265+ m_allocatedChunks.insert(pChunk->chunk_number, pChunk);
266+
267+ // Insert the chunk into the LRU list
268+ m_mruChunk = insertIntoLRUList(pChunk, m_mruChunk);
269+
270+ // If this chunk has no next LRU then it is the LRU. This only
271+ // happens if this is the first allocated chunk.
272+ if (pChunk->next_lru == NULL) {
273+ m_lruChunk = pChunk;
274+ }
275+ }
276+ } else if (status.status == CHUNK_READ_EOF) {
277+ Chunk* pChunk = status.chunk;
278+ Q_ASSERT(pChunk != NULL);
279+ Chunk* pChunk2 = m_chunksBeingRead.take(pChunk->chunk_number);
280+ if (pChunk2 != pChunk) {
281+ qDebug() << "Mismatch in requested chunk to read!";
282+ }
283+ freeChunk(pChunk);
284+ } else if (status.status == CHUNK_READ_INVALID) {
285+ qDebug() << "WARNING: READER THREAD RECEIVED INVALID CHUNK READ";
286+ Chunk* pChunk = status.chunk;
287+ Q_ASSERT(pChunk != NULL);
288+ Chunk* pChunk2 = m_chunksBeingRead.take(pChunk->chunk_number);
289+ if (pChunk2 != pChunk) {
290+ qDebug() << "Mismatch in requested chunk to read!";
291+ }
292+ freeChunk(pChunk);
293+ }
294+ }
295+}
296+
297 int CachingReader::read(int sample, int num_samples, CSAMPLE* buffer) {
298 int zerosWritten = 0;
299 // Check for bogus sample numbers
300@@ -305,11 +341,13 @@
301 // If asked to read 0 samples, don't do anything. (this is a perfectly
302 // reasonable request that happens sometimes. If no track is loaded, don't
303 // do anything.
304- if (num_samples == 0 ||
305- m_iTrackSampleRate == 0) {
306+ if (num_samples == 0 || m_readerStatus != TRACK_LOADED) {
307 return 0;
308 }
309
310+ // Process messages from the reader thread.
311+ process();
312+
313 // TODO: is it possible to move this code out of caching reader
314 // and into enginebuffer? It doesn't quite make sense here, although
315 // it makes preroll completely transparent to the rest of the code
316@@ -331,8 +369,12 @@
317 }
318 }
319
320- int start_chunk = chunkForSample(sample);
321- int end_chunk = chunkForSample(sample + num_samples - 1);
322+ int start_sample = math_min(m_iTrackNumSamplesCallbackSafe,
323+ sample);
324+ int start_chunk = chunkForSample(start_sample);
325+ int end_sample = math_min(m_iTrackNumSamplesCallbackSafe,
326+ sample + num_samples - 1);
327+ int end_chunk = chunkForSample(end_sample);
328
329 int samples_remaining = num_samples;
330 int current_sample = sample;
331@@ -340,26 +382,15 @@
332 // Sanity checks
333 Q_ASSERT(start_chunk <= end_chunk);
334
335- bool cache_miss = false;
336- // Need to lock while we're touching Chunk's
337- if (!m_readerMutex.tryLock()) {
338- //qDebug() << m_pGroup << "CachingReader::read() blocked";
339- m_readerMutex.lock();
340- }
341-
342- //qDebug() << m_pGroup << "CachingReader::read() lock acquired";
343-
344 for (int chunk_num = start_chunk; chunk_num <= end_chunk; chunk_num++) {
345- Chunk* current = getChunk(chunk_num, &cache_miss);
346-
347- if (cache_miss) {
348- //qDebug() << m_pGroup << "Cache miss in read() on chunk" << chunk_num;
349- }
350-
351- // getChunk gets a chunk at any cost. If it has failed to lookup the
352- // chunk, then there is a serious issue.
353+ Chunk* current = lookupChunk(chunk_num);
354+
355+ // If the chunk is not in cache, then we must return an error.
356 if (current == NULL) {
357- qDebug() << "Couldn't get chunk " << start_chunk << " in read()";
358+ qDebug() << "Couldn't get chunk " << chunk_num
359+ << " in read() of [" << sample << "," << sample+num_samples
360+ << "] chunks " << start_chunk << "-" << end_chunk;
361+
362 // Something is wrong. Break out of the loop, that should fill the
363 // samples requested with zeroes.
364 break;
365@@ -408,9 +439,6 @@
366 current_sample += samples_to_read;
367 samples_remaining -= samples_to_read;
368 }
369- //qDebug() << m_pGroup << "CachingReader::read() unlocked";
370- m_readerMutex.unlock();
371-
372
373 // If we didn't supply all the samples requested, that probably means we're
374 // at the end of the file, or something is wrong. Provide zeroes and pretend
375@@ -425,21 +453,9 @@
376 return zerosWritten + num_samples - samples_remaining;
377 }
378
379-void CachingReader::hint(Hint& hint) {
380-}
381-
382-void CachingReader::hint(QList<Hint>& hintList) {
383-}
384-
385 void CachingReader::hintAndMaybeWake(QList<Hint>& hintList) {
386- if (!m_readerMutex.tryLock()) {
387- //qDebug() << m_pGroup << "CachingReader::hintAndMaybeWake would have blocked";
388- return;
389- }
390-
391 // If no file is loaded, skip.
392- if (m_iTrackSampleRate == 0) {
393- m_readerMutex.unlock();
394+ if (m_readerStatus != TRACK_LOADED) {
395 return;
396 }
397
398@@ -456,7 +472,7 @@
399 const int default_samples = 2048;
400
401 QSet<int> chunksToFreshen;
402- while(iterator.hasNext()) {
403+ while (iterator.hasNext()) {
404 // Copy, don't use reference.
405 Hint hint = iterator.next();
406
407@@ -472,8 +488,12 @@
408 }
409 //Q_ASSERT(hint.sample >= 0);
410 Q_ASSERT(hint.length >= 0);
411- int start_chunk = chunkForSample(hint.sample);
412- int end_chunk = chunkForSample(hint.sample + hint.length);
413+ int start_sample = math_min(m_iTrackNumSamplesCallbackSafe,
414+ hint.sample);
415+ int start_chunk = chunkForSample(start_sample);
416+ int end_sample = math_min(m_iTrackNumSamplesCallbackSafe,
417+ hint.sample + hint.length - 1);
418+ int end_chunk = chunkForSample(end_sample);
419
420 for (int current = start_chunk; current <= end_chunk; ++current) {
421 chunksToFreshen.insert(current);
422@@ -484,21 +504,30 @@
423 // any are not, then wake.
424 bool shouldWake = false;
425 QSetIterator<int> setIterator(chunksToFreshen);
426- while(setIterator.hasNext()) {
427+ while (setIterator.hasNext()) {
428 int chunk = setIterator.next();
429
430 // This will cause the chunk to be 'freshened' in the cache. The
431 // chunk will be moved to the end of the LRU list.
432- if (lookupChunk(chunk) == NULL) {
433+ if (!m_chunksBeingRead.contains(chunk) && lookupChunk(chunk) == NULL) {
434 shouldWake = true;
435- m_chunksToRead.insert(chunk);
436+ Chunk* pChunk = allocateChunkExpireLRU();
437+ Q_ASSERT(pChunk != NULL);
438+
439+ m_chunksBeingRead.insert(chunk, pChunk);
440+ ChunkReadRequest request;
441+ pChunk->chunk_number = chunk;
442+ request.chunk = pChunk;
443+ // qDebug() << "Requesting read of chunk" << chunk << "into" << pChunk;
444+ // qDebug() << "Requesting read into " << request.chunk->data;
445+ if (m_chunkReadRequestFIFO.write(&request, 1) != 1) {
446+ qDebug() << "ERROR: Could not submit read request for "
447+ << chunk;
448+ }
449 //qDebug() << "Checking chunk " << chunk << " shouldWake:" << shouldWake << " chunksToRead" << m_chunksToRead.size();
450 }
451-
452 }
453
454- m_readerMutex.unlock();
455-
456 // If there are chunks to be read, wake up.
457 if (shouldWake) {
458 wake();
459@@ -521,16 +550,12 @@
460 loadTrack(pLoadTrack);
461 } else {
462 // Read the requested chunks.
463- m_readerMutex.lock();
464- //qDebug() << m_pGroup << "CachingReader::run() lock acquired, reading" << m_chunksToRead.size() << "chunks";
465- for (QSet<int>::iterator it = m_chunksToRead.begin();
466- it != m_chunksToRead.end(); it++) {
467- int chunk = *it;
468- getChunk(chunk);
469+ ChunkReadRequest request;
470+ ReaderStatusUpdate status;
471+ while (m_chunkReadRequestFIFO.read(&request, 1) == 1) {
472+ processChunkReadRequest(&request, &status);
473+ m_readerStatusFIFO.writeBlocking(&status, 1);
474 }
475- m_chunksToRead.clear();
476- //qDebug() << m_pGroup << "CachingReader::run() unlocked";
477- m_readerMutex.unlock();
478 }
479
480 // Notify the EngineWorkerScheduler that the work we did is done.
481@@ -543,9 +568,12 @@
482 }
483
484 void CachingReader::loadTrack(TrackPointer pTrack) {
485- m_readerMutex.lock();
486 //qDebug() << m_pGroup << "CachingReader::loadTrack() lock acquired for load.";
487- freeAllChunks();
488+
489+ ReaderStatusUpdate status;
490+ status.status = TRACK_LOADED;
491+ status.chunk = NULL;
492+ status.trackNumSamples = 0;
493
494 if (m_pCurrentSoundSource != NULL) {
495 delete m_pCurrentSoundSource;
496@@ -560,7 +588,8 @@
497 // Must unlock before emitting to avoid deadlock
498 qDebug() << m_pGroup << "CachingReader::loadTrack() load failed for\""
499 << filename << "\", unlocked reader lock";
500- m_readerMutex.unlock();
501+ status.status = TRACK_NOT_LOADED;
502+ m_readerStatusFIFO.writeBlocking(&status, 1);
503 emit(trackLoadFailed(
504 pTrack, QString("The file '%1' could not be found.").arg(filename)));
505 return;
506@@ -568,43 +597,32 @@
507
508 m_pCurrentSoundSource = new SoundSourceProxy(pTrack);
509 m_pCurrentSoundSource->open(); //Open the song for reading
510- m_pCurrentTrack = pTrack;
511 m_iTrackSampleRate = m_pCurrentSoundSource->getSampleRate();
512- m_iTrackNumSamples = m_pCurrentSoundSource->length();
513+ m_iTrackNumSamples = status.trackNumSamples =
514+ m_pCurrentSoundSource->length();
515
516 if (m_iTrackNumSamples == 0 || m_iTrackSampleRate == 0) {
517 // Must unlock before emitting to avoid deadlock
518 qDebug() << m_pGroup << "CachingReader::loadTrack() load failed for\""
519 << filename << "\", file invalid, unlocked reader lock";
520- m_readerMutex.unlock();
521+ status.status = TRACK_NOT_LOADED;
522+ m_readerStatusFIFO.writeBlocking(&status, 1);
523 emit(trackLoadFailed(
524 pTrack, QString("The file '%1' could not be loaded.").arg(filename)));
525 return;
526 }
527
528+ m_readerStatusFIFO.writeBlocking(&status, 1);
529+
530 // Clear the chunks to read list.
531- m_chunksToRead.clear();
532-
533- // Must unlock before emitting to avoid deadlock
534- //qDebug() << m_pGroup << "CachingReader::loadTrack() unlocked reader lock";
535- m_readerMutex.unlock();
536+ ChunkReadRequest request;
537+ while (m_chunkReadRequestFIFO.read(&request, 1) == 1) {
538+ qDebug() << "Skipping read request for " << request.chunk->chunk_number;
539+ status.status = CHUNK_READ_INVALID;
540+ status.chunk = request.chunk;
541+ m_readerStatusFIFO.writeBlocking(&status, 1);
542+ }
543
544 // Emit that the track is loaded.
545 emit(trackLoaded(pTrack, m_iTrackSampleRate, m_iTrackNumSamples));
546 }
547-
548-
549-int CachingReader::getTrackSampleRate() {
550- m_readerMutex.lock();
551- int value = m_iTrackSampleRate;
552- m_readerMutex.unlock();
553- return value;
554-}
555-
556-int CachingReader::getTrackNumSamples() {
557- m_readerMutex.lock();
558- int value = m_iTrackNumSamples;
559- m_readerMutex.unlock();
560- return value;
561-}
562-
563
564=== modified file 'mixxx/src/cachingreader.h'
565--- mixxx/src/cachingreader.h 2011-03-09 21:58:35 +0000
566+++ mixxx/src/cachingreader.h 2011-10-04 14:01:27 +0000
567@@ -18,6 +18,7 @@
568 #include "configobject.h"
569 #include "trackinfoobject.h"
570 #include "engine/engineworker.h"
571+#include "util/fifo.h"
572
573 class ControlObjectThread;
574 namespace Mixxx {
575@@ -52,6 +53,31 @@
576 Chunk* next_lru;
577 } Chunk;
578
579+typedef struct ChunkReadRequest {
580+ Chunk* chunk;
581+
582+ ChunkReadRequest() { chunk = NULL; }
583+} ChunkReadRequest;
584+
585+enum ReaderStatus {
586+ INVALID,
587+ TRACK_NOT_LOADED,
588+ TRACK_LOADED,
589+ CHUNK_READ_SUCCESS,
590+ CHUNK_READ_EOF,
591+ CHUNK_READ_INVALID
592+};
593+
594+typedef struct ReaderStatusUpdate {
595+ ReaderStatus status;
596+ Chunk* chunk;
597+ int trackNumSamples;
598+ ReaderStatusUpdate() {
599+ status = INVALID;
600+ chunk = NULL;
601+ }
602+} ReaderStatusUpdate;
603+
604 // CachingReader provides a layer on top of a SoundSource for reading samples
605 // from a file. A cache is provided so that repeated reads to a certain section
606 // of a song do not cause disk seeks or unnecessary SoundSource
607@@ -63,33 +89,21 @@
608 Q_OBJECT
609
610 public:
611-
612 // Construct a CachingReader with the given group.
613 CachingReader(const char* _group,
614 ConfigObject<ConfigValue>* _config);
615 virtual ~CachingReader();
616
617- // You really shouldn't use these unless there /really/ isn't any other way
618- // of getting at this data. Calling it involves a lock/unlock. It's better
619- // to receive trackLoaded signals instead.
620- int getTrackSampleRate();
621- int getTrackNumSamples();
622+ void process();
623
624 // Read num_samples from the SoundSource starting with sample into
625 // buffer. Returns the total number of samples actually written to buffer.
626 int read(int sample, int num_samples, CSAMPLE* buffer);
627
628-
629- // Issue a hint or list of hints to the CachingReader. Next time the reader
630- // is woken, it will make sure as many hints are in memory as possible. The
631- // hint's sample and length are taken into account, but priority is
632- // currently unused.
633- void hint(Hint& hint);
634- void hint(QList<Hint>& hintList);
635-
636 // Issue a list of hints, but check whether any of the hints request a chunk
637 // that is not in the cache. If any hints do request a chunk not in cache,
638- // then wake the reader so that it can process them.
639+ // then wake the reader so that it can process them. Must only be called
640+ // from the engine callback.
641 void hintAndMaybeWake(QList<Hint>& hintList);
642
643 // Request that the CachingReader load a new track. These requests are
644@@ -104,55 +118,51 @@
645 // thread pool via the EngineWorkerScheduler.
646 void run();
647
648+ // A Chunk is a memory-resident section of audio that has been cached. Each
649+ // chunk holds a fixed number of samples given by kSamplesPerChunk.
650+ const static int kChunkLength, kSamplesPerChunk;
651+
652 signals:
653 // Emitted once a new track is loaded and ready to be read from.
654 void trackLoaded(TrackPointer pTrack, int iSampleRate, int iNumSamples);
655 void trackLoadFailed(TrackPointer pTrack, QString reason);
656
657 private:
658-
659- // A Chunk is a memory-resident section of audio that has been cached. Each
660- // chunk holds a fixed number of samples given by kSamplesPerChunk.
661- const static int kChunkLength, kSamplesPerChunk;
662-
663 // Removes a chunk from the LRU list
664 static Chunk* removeFromLRUList(Chunk* chunk, Chunk* head);
665 // Inserts a chunk into the LRU list
666 static Chunk* insertIntoLRUList(Chunk* chunk, Chunk* head);
667
668+ // Given a sample number, return the chunk number corresponding to it.
669+ inline static int chunkForSample(int sample_number) {
670+ return sample_number / kSamplesPerChunk;
671+ }
672+
673+ // Given a chunk number, return the start sample number for the chunk.
674+ inline static int sampleForChunk(int chunk_number) {
675+ return chunk_number * kSamplesPerChunk;
676+ }
677+
678 // Initialize the reader by creating all the chunks from the RAM provided to
679 // the CachingReader.
680 void initialize();
681
682- // Internal method to load a track. Emits trackLoaded when finished.
683- void loadTrack(TrackPointer pTrack);
684-
685- // Queue of Tracks to load, and the corresponding lock.
686+ const char* m_pGroup;
687+ const ConfigObject<ConfigValue>* m_pConfig;
688+
689+ // Thread-safe FIFOs for communication between the engine callback and
690+ // reader thread.
691+ FIFO<ChunkReadRequest> m_chunkReadRequestFIFO;
692+ FIFO<ReaderStatusUpdate> m_readerStatusFIFO;
693+
694+ // Queue of Tracks to load, and the corresponding lock. Must acquire the
695+ // lock to touch.
696 QMutex m_trackQueueMutex;
697 QQueue<TrackWeakPointer> m_trackQueue;
698
699- // Held when the Reader is working: read() vs. run()
700- QMutex m_readerMutex;
701- QWaitCondition m_readerWait;
702-
703- //
704- // Everything below this line is guarded by m_readerMutex, make sure you
705- // hold it if you touch them.
706- //
707-
708- // Chunks that should be read into memory when the CachingReader wakes
709- // up. Readers and writers must holds the reader lock.
710- QSet<int> m_chunksToRead;
711-
712- // Look up chunk_number at any cost. Reads from SoundSource if
713- // necessary. Returns NULL if chunk_number is not valid. If cache_miss is
714- // not NULL, sets it to true or false whether or not the chunk was fetched
715- // from cache or not.
716- Chunk* getChunk(int chunk_number, bool* cache_miss=NULL);
717-
718- // Read the given chunk_number from the file into pChunk's data
719- // buffer. Fills length/sample information about Chunk* as well.
720- bool readChunkFromFile(Chunk* pChunk, int chunk_number);
721+ ////////////////////////////////////////////////////////////////////////////
722+ // The following may /only/ be called within the engine callback
723+ ////////////////////////////////////////////////////////////////////////////
724
725 // Looks for the provided chunk number in the index of in-memory chunks and
726 // returns it if it is present. If not, returns NULL.
727@@ -170,37 +180,15 @@
728 // Gets a chunk from the free list, frees the LRU Chunk if none available.
729 Chunk* allocateChunkExpireLRU();
730
731- // Given a sample number, return the chunk number corresponding to it.
732- inline int chunkForSample(int sample_number) {
733- return sample_number / kSamplesPerChunk;
734- //return int(floor(double(sample_number) / double(kSamplesPerChunk)));
735- }
736-
737- // Given a chunk number, return the start sample number for the chunk.
738- inline int sampleForChunk(int chunk_number) {
739- return chunk_number * kSamplesPerChunk;
740- }
741-
742- inline bool isChunkValid(int chunk_number) {
743- return m_iTrackSampleRate != 0 &&
744- chunk_number >= 0 &&
745- sampleForChunk(chunk_number) < m_iTrackNumSamples;
746- }
747-
748- const char* m_pGroup;
749- const ConfigObject<ConfigValue>* m_pConfig;
750-
751- // The current track loaded
752- TrackPointer m_pCurrentTrack;
753- // The current sound source of the track loaded
754- Mixxx::SoundSource* m_pCurrentSoundSource;
755- int m_iTrackSampleRate;
756- int m_iTrackNumSamples;
757+ ReaderStatus m_readerStatus;
758
759 // Keeps track of free Chunks we've allocated
760 QVector<Chunk*> m_chunks;
761 // List of free chunks available for use.
762 QList<Chunk*> m_freeChunks;
763+ // List of reserved chunks with reads in progress
764+ QHash<int, Chunk*> m_chunksBeingRead;
765+
766 // Keeps track of what Chunks we've allocated and indexes them based on what
767 // chunk number they are allocated to.
768 QHash<int, Chunk*> m_allocatedChunks;
769@@ -211,10 +199,27 @@
770
771 // The raw memory buffer which is divided up into chunks.
772 CSAMPLE* m_pRawMemoryBuffer;
773- int m_iRawMemoryBufferLength;
774+
775+ ////////////////////////////////////////////////////////////////////////////
776+ // The following may /only/ be called within the reader thread
777+ ////////////////////////////////////////////////////////////////////////////
778+
779+ // Internal method to load a track. Emits trackLoaded when finished.
780+ void loadTrack(TrackPointer pTrack);
781+
782+ // Read the given chunk_number from the file into pChunk's data
783+ // buffer. Fills length/sample information about Chunk* as well.
784+ void processChunkReadRequest(ChunkReadRequest* request,
785+ ReaderStatusUpdate* update);
786+
787+ // The current sound source of the track loaded
788+ Mixxx::SoundSource* m_pCurrentSoundSource;
789+ int m_iTrackSampleRate;
790+ int m_iTrackNumSamples;
791+ int m_iTrackNumSamplesCallbackSafe;
792+
793 // Temporary buffer for reading from SoundSources
794 SAMPLE* m_pSample;
795- bool m_bQuit;
796 };
797
798
799
800=== modified file 'mixxx/src/dlgprefnovinyldlg.ui'
801--- mixxx/src/dlgprefnovinyldlg.ui 2011-05-26 10:22:03 +0000
802+++ mixxx/src/dlgprefnovinyldlg.ui 2011-10-04 14:01:27 +0000
803@@ -317,6 +317,13 @@
804 </property>
805 </spacer>
806 </item>
807+ <item row="3" column="0" colspan="3">
808+ <widget class="QCheckBox" name="SignalQualityEnable">
809+ <property name="text">
810+ <string>Show Signal Quality in Skin</string>
811+ </property>
812+ </widget>
813+ </item>
814 </layout>
815 </widget>
816 </item>
817@@ -406,6 +413,22 @@
818 </property>
819 </spacer>
820 </item>
821+ <item row="1" column="0">
822+ <widget class="QLabel" name="label">
823+ <property name="enabled">
824+ <bool>false</bool>
825+ </property>
826+ <property name="toolTip">
827+ <string>http://www.xwax.co.uk</string>
828+ </property>
829+ <property name="text">
830+ <string>Powered by xwax</string>
831+ </property>
832+ <property name="alignment">
833+ <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
834+ </property>
835+ </widget>
836+ </item>
837 </layout>
838 </widget>
839 </item>
840
841=== modified file 'mixxx/src/dlgprefvinyl.cpp'
842--- mixxx/src/dlgprefvinyl.cpp 2011-05-11 04:38:17 +0000
843+++ mixxx/src/dlgprefvinyl.cpp 2011-10-04 14:01:27 +0000
844@@ -137,6 +137,8 @@
845
846 // Honour the Needle Skip Prevention setting.
847 NeedleSkipEnable->setChecked( (bool)config->getValueString( ConfigKey("[VinylControl]", "needle_skip_prevention") ).toInt() );
848+
849+ SignalQualityEnable->setChecked((bool)config->getValueString(ConfigKey("[VinylControl]", "show_signal_quality") ).toInt() );
850
851 //set vinyl control gain
852 VinylGain->setValue( config->getValueString(ConfigKey("[VinylControl]","gain")).toInt());
853@@ -182,6 +184,7 @@
854 m_COMode.slotSet(iMode);
855 config->set(ConfigKey("[VinylControl]","mode"), ConfigValue(iMode));
856 config->set(ConfigKey("[VinylControl]","needle_skip_prevention" ), ConfigValue( (int)(NeedleSkipEnable->isChecked( )) ) );
857+ config->set(ConfigKey("[VinylControl]","show_signal_quality" ), ConfigValue( (int)(SignalQualityEnable->isChecked( )) ) );
858
859 m_pVCManager->reloadConfig();
860 slotUpdate();
861
862=== modified file 'mixxx/src/dlgprefvinyldlg.ui'
863--- mixxx/src/dlgprefvinyldlg.ui 2011-05-26 10:22:03 +0000
864+++ mixxx/src/dlgprefvinyldlg.ui 2011-10-04 14:01:27 +0000
865@@ -308,6 +308,13 @@
866 </property>
867 </spacer>
868 </item>
869+ <item row="3" column="0" colspan="3">
870+ <widget class="QCheckBox" name="SignalQualityEnable">
871+ <property name="text">
872+ <string>Show Signal Quality in Skin</string>
873+ </property>
874+ </widget>
875+ </item>
876 </layout>
877 </widget>
878 </item>
879
880=== modified file 'mixxx/src/engine/enginebuffer.cpp'
881--- mixxx/src/engine/enginebuffer.cpp 2011-05-01 19:10:21 +0000
882+++ mixxx/src/engine/enginebuffer.cpp 2011-10-04 14:01:27 +0000
883@@ -336,7 +336,6 @@
884 m_pTrackSampleRate->set(iTrackSampleRate);
885 slotControlSeek(0.);
886
887-
888 // Let the engine know that a track is loaded now.
889 m_pTrackEndCOT->slotSet(0.0f); //XXX: Not sure if to use the COT or CO here
890
891@@ -455,6 +454,7 @@
892 void EngineBuffer::process(const CSAMPLE *, const CSAMPLE * pOut, const int iBufferSize)
893 {
894
895+ m_pReader->process();
896 // Steps:
897 // - Lookup new reader information
898 // - Calculate current rate
899@@ -613,10 +613,6 @@
900 m_engineLock.unlock();
901
902
903- // Give the Reader hints as to which chunks of the current song we
904- // really care about. It will try very hard to keep these in memory
905- hintReader(rate, iBufferSize);
906-
907 // Update all the indicators that EngineBuffer publishes to allow
908 // external parts of Mixxx to observe its status.
909 updateIndicators(rate, iBufferSize);
910@@ -648,9 +644,9 @@
911 bCurBufferPaused = true;
912 }
913
914- // Wake up the reader so that it processes our hints / loads new files
915- // (hopefully) before the next callback.
916- m_pReader->wake();
917+ // Give the Reader hints as to which chunks of the current song we
918+ // really care about. It will try very hard to keep these in memory
919+ hintReader(rate, iBufferSize);
920
921 if (m_bLastBufferPaused && !bCurBufferPaused) {
922 if (fabs(rate) > 0.005) //at very slow forward rates, don't ramp up
923@@ -780,7 +776,7 @@
924 m_engineLock.lock();
925
926 m_hintList.clear();
927- m_pReadAheadManager->hintReader(m_hintList, iSourceSamples);
928+ m_pReadAheadManager->hintReader(dRate, m_hintList, iSourceSamples);
929
930 QListIterator<EngineControl*> it(m_engineControls);
931 while (it.hasNext()) {
932
933=== modified file 'mixxx/src/engine/enginebufferscalelinear.cpp'
934--- mixxx/src/engine/enginebufferscalelinear.cpp 2011-04-16 20:51:48 +0000
935+++ mixxx/src/engine/enginebufferscalelinear.cpp 2011-10-04 14:01:27 +0000
936@@ -326,16 +326,6 @@
937 //at extremely low speeds, dampen the gain to hide pops and clicks
938 //this does cause odd-looking linear waveforms that go to zero and back
939
940- //although enginevinylsoundemu does this, it works much better here
941- //because the gain ramps as the rate does
942- if (fabs(rate_add) < 0.5) {
943- float dither = (float)(rand() % 32768) / 32768 - 0.5; // dither
944- //float dither = 0;
945- float gainfrac = fabs(rate_add) / 0.5;
946- buf[i] = gainfrac * (float)buf[i] + dither;
947- buf[i+1] = gainfrac * (float)buf[i+1] + dither;
948- }
949-
950 /*writer << QString("%1,%2,%3,%4\n").arg(buffer_count)
951 .arg(buffer[i])
952 .arg(prev_sample[0])
953
954=== modified file 'mixxx/src/engine/enginebufferscalest.h'
955--- mixxx/src/engine/enginebufferscalest.h 2009-08-03 06:50:02 +0000
956+++ mixxx/src/engine/enginebufferscalest.h 2011-10-04 14:01:27 +0000
957@@ -30,7 +30,7 @@
958 #include "SoundTouch.h"
959
960 /** Number of samples to read ahead */
961-const int kiSoundTouchReadAheadLength = 1000;
962+const int kiSoundTouchReadAheadLength = 10000;
963 using namespace soundtouch;
964
965 class ReadAheadManager;
966@@ -40,7 +40,7 @@
967 */
968 class EngineBufferScaleST : public EngineBufferScale {
969 Q_OBJECT
970-public:
971+public:
972 EngineBufferScaleST(ReadAheadManager* pReadAheadManager);
973 ~EngineBufferScaleST();
974
975@@ -61,13 +61,13 @@
976 /** Flush buffer */
977 void clear();
978
979-public slots:
980+public slots:
981 void slotSetSamplerate(double dSampleRate);
982 private:
983 /** Holds the playback direction */
984 bool m_bBackwards;
985
986- /** Buffer used to reverse output from SoundTouch
987+ /** Buffer used to reverse output from SoundTouch
988 * library when playback direction is backwards */
989 CSAMPLE *buffer_back;
990
991@@ -79,7 +79,7 @@
992
993 /** Used when clear is called */
994 bool m_bClear;
995-
996+
997 /** Used to protect SoundTouch calls */
998 QMutex m_qMutex;
999
1000
1001=== modified file 'mixxx/src/engine/enginedeck.cpp'
1002--- mixxx/src/engine/enginedeck.cpp 2011-04-15 22:19:45 +0000
1003+++ mixxx/src/engine/enginedeck.cpp 2011-10-04 14:01:27 +0000
1004@@ -54,8 +54,7 @@
1005 // Process the raw audio
1006 m_pBuffer->process(0, pOut, iBufferSize);
1007 // Emulate vinyl sounds
1008- // This causes popping, disable -Owen
1009- //m_pVinylSoundEmu->process(pOut, pOut, iBufferSize);
1010+ m_pVinylSoundEmu->process(pOut, pOut, iBufferSize);
1011 // Apply pregain
1012 m_pPregain->process(pOut, pOut, iBufferSize);
1013 // Filter the channel with EQs
1014
1015=== modified file 'mixxx/src/engine/enginemaster.cpp'
1016--- mixxx/src/engine/enginemaster.cpp 2011-05-01 19:06:55 +0000
1017+++ mixxx/src/engine/enginemaster.cpp 2011-10-04 14:01:27 +0000
1018@@ -32,6 +32,7 @@
1019 #include "enginevumeter.h"
1020 #include "enginexfader.h"
1021 #include "enginesidechain.h"
1022+#include "engine/syncworker.h"
1023 #include "sampleutil.h"
1024
1025 #ifdef __LADSPA__
1026@@ -43,6 +44,8 @@
1027 const char * group) {
1028
1029 m_pWorkerScheduler = new EngineWorkerScheduler(this);
1030+ m_pWorkerScheduler->start();
1031+ m_pSyncWorker = new SyncWorker(m_pWorkerScheduler);
1032
1033 // Master sample rate
1034 ControlObject * sr = new ControlObject(ConfigKey(group, "samplerate"));
1035@@ -132,6 +135,9 @@
1036 delete pChannelInfo->m_pVolumeControl;
1037 delete pChannelInfo;
1038 }
1039+
1040+ delete m_pWorkerScheduler;
1041+ delete m_pSyncWorker;
1042 }
1043
1044 const CSAMPLE* EngineMaster::getMasterBuffer() const
1045@@ -409,8 +415,11 @@
1046 //Master/headphones interleaving is now done in
1047 //SoundManager::requestBuffer() - Albert Nov 18/07
1048
1049- // We're close to the end of the callback. Schedule the workers. Hopefully
1050- // the work thread doesn't get scheduled between now and then.
1051+ // Schedule a ControlObject sync
1052+ m_pSyncWorker->schedule();
1053+
1054+ // We're close to the end of the callback. Wake up the engine worker
1055+ // scheduler so that it runs the workers.
1056 m_pWorkerScheduler->runWorkers();
1057 }
1058
1059
1060=== modified file 'mixxx/src/engine/enginemaster.h'
1061--- mixxx/src/engine/enginemaster.h 2011-04-13 03:01:52 +0000
1062+++ mixxx/src/engine/enginemaster.h 2011-10-04 14:01:27 +0000
1063@@ -39,6 +39,7 @@
1064 class ControlPushButton;
1065 class EngineVinylSoundEmu;
1066 class EngineSideChain;
1067+class SyncWorker;
1068
1069 class EngineMaster : public EngineObject, public AudioSource {
1070 Q_OBJECT
1071@@ -131,6 +132,7 @@
1072 CSAMPLE *m_pMaster, *m_pHead;
1073
1074 EngineWorkerScheduler *m_pWorkerScheduler;
1075+ SyncWorker* m_pSyncWorker;
1076
1077 ControlObject *m_pMasterVolume, *m_pHeadVolume;
1078 EngineClipping *clipping, *head_clipping;
1079
1080=== modified file 'mixxx/src/engine/enginevinylsoundemu.cpp'
1081--- mixxx/src/engine/enginevinylsoundemu.cpp 2010-09-17 04:15:33 +0000
1082+++ mixxx/src/engine/enginevinylsoundemu.cpp 2011-10-04 14:01:27 +0000
1083@@ -23,7 +23,7 @@
1084
1085 /** This class emulates the response of a vinyl record's audio to changes
1086 * in speed. In practice, it quiets the audio during very slow playback.
1087- * This also helps mask the aliasing due to interpolation that occurs at
1088+ * Dithering also helps mask the aliasing due to interpolation that occurs at
1089 * these slow speeds.
1090 */
1091
1092@@ -31,8 +31,14 @@
1093 {
1094 m_pConfig = pConfig;
1095 m_pRateEngine = ControlObject::getControl(ConfigKey(group, "rateEngine"));
1096- m_fAbsSpeed = 0.0f;
1097+ m_fSpeed = m_fOldSpeed = 0.0f;
1098 m_fGainFactor = 1.0f;
1099+ m_iNoisePos = 0;
1100+
1101+ for (int i=0; i<NOISE_BUFFER_SIZE; i++)
1102+ {
1103+ m_fNoise[i] = (float)(rand() % 32768) / 32768 - 0.5;
1104+ }
1105 }
1106
1107 EngineVinylSoundEmu::~EngineVinylSoundEmu()
1108@@ -43,30 +49,36 @@
1109 void EngineVinylSoundEmu::process(const CSAMPLE * pIn, const CSAMPLE * pOut, const int iBufferSize)
1110 {
1111 CSAMPLE * pOutput = (CSAMPLE *)pOut;
1112- m_fAbsSpeed = fabs((float)m_pRateEngine->get());
1113- //qDebug() << m_pRateEngine->get();
1114+ m_fSpeed = (float)m_pRateEngine->get();
1115+ float rateFrac = 2 * (m_fSpeed - m_fOldSpeed) / (float)iBufferSize;
1116+ float curRate = m_fOldSpeed;
1117
1118 const float thresholdSpeed = 0.070f; //Scale volume if playback speed is below 7%.
1119- if (m_fAbsSpeed < thresholdSpeed && m_fAbsSpeed > 0.0f) //Change the volume based on the playback speed.
1120- {
1121- //The numbers in this formula are important:
1122- // - The "1 + ..." makes the minimum value of the parameter of log10
1123- // be 1, which makes the gain 0.
1124- // - The "* 9" makes the maximum value of the log10 become 10 (9 + 1 = 10)
1125- // which gives a gain of 1
1126- //m_fGainFactor = log10(1 + m_fAbsSpeed/0.50f * 9);
1127- m_fGainFactor = m_fAbsSpeed/thresholdSpeed;
1128- //qDebug() << m_fGainFactor << m_fAbsSpeed;
1129- }
1130- else if (m_fAbsSpeed == 0.0f)
1131- m_fGainFactor = 0.0f; //Log blows up at 0 :)
1132- else
1133- {
1134- m_fGainFactor = 1.0f;
1135- }
1136-
1137- // Apply whatever gain we calculated. SampleUtil takes care of aliased
1138- // buffers and gains of 1 or 0.
1139- SampleUtil::copyWithGain(pOutput, pIn, m_fGainFactor, iBufferSize);
1140+ const float ditherSpeed = 0.85f; //Dither if playback speed is below 85%.
1141+
1142+ //iterate over old rate to new rate to prevent audible pops
1143+ for (int i=0; i<iBufferSize; i+=2)
1144+ {
1145+ float absCurRate = fabs(curRate);
1146+ float dither = 0;
1147+ if (absCurRate < ditherSpeed) {
1148+ dither = m_fNoise[m_iNoisePos];
1149+ m_iNoisePos = (m_iNoisePos + 1) % NOISE_BUFFER_SIZE;
1150+ }
1151+
1152+ if (absCurRate < thresholdSpeed) {
1153+ float gainfrac = absCurRate / thresholdSpeed;
1154+ pOutput[i] = gainfrac * (float)pIn[i] + dither;
1155+ pOutput[i+1] = gainfrac * (float)pIn[i+1] + dither;
1156+ }
1157+ else
1158+ {
1159+ pOutput[i] = pIn[i] + dither;
1160+ pOutput[i+1] = pIn[i+1] + dither;
1161+ }
1162+
1163+ curRate += rateFrac;
1164+ }
1165+ m_fOldSpeed = m_fSpeed;
1166 }
1167
1168
1169=== modified file 'mixxx/src/engine/enginevinylsoundemu.h'
1170--- mixxx/src/engine/enginevinylsoundemu.h 2009-01-24 04:39:32 +0000
1171+++ mixxx/src/engine/enginevinylsoundemu.h 2011-10-04 14:01:27 +0000
1172@@ -19,6 +19,7 @@
1173
1174 #include "engineobject.h"
1175
1176+#define NOISE_BUFFER_SIZE 1000
1177 class EngineVinylSoundEmu : public EngineObject
1178 {
1179 public:
1180@@ -28,8 +29,12 @@
1181 private:
1182 ConfigObject<ConfigValue> *m_pConfig;
1183 ControlObject *m_pRateEngine;
1184- float m_fAbsSpeed;
1185+ float m_fSpeed, m_fOldSpeed;
1186 float m_fGainFactor;
1187+
1188+ float m_fNoise[NOISE_BUFFER_SIZE];
1189+ int m_iNoisePos;
1190 };
1191
1192+
1193 #endif
1194
1195=== modified file 'mixxx/src/engine/engineworkerscheduler.cpp'
1196--- mixxx/src/engine/engineworkerscheduler.cpp 2010-10-19 01:35:26 +0000
1197+++ mixxx/src/engine/engineworkerscheduler.cpp 2011-10-04 14:01:27 +0000
1198@@ -8,11 +8,14 @@
1199 #include "engine/engineworkerscheduler.h"
1200
1201 EngineWorkerScheduler::EngineWorkerScheduler(QObject* pParent)
1202- : QThreadPool(pParent) {
1203+ : m_scheduleFIFO(MAX_ENGINE_WORKERS) {
1204+ Q_UNUSED(pParent);
1205+ m_workerThreadPool.setMaxThreadCount(ENGINE_WORKER_THREAD_COUNT);
1206+ // A timeout of 1 minute for threads in the pool.
1207+ m_workerThreadPool.setExpiryTimeout(60000);
1208 }
1209
1210 EngineWorkerScheduler::~EngineWorkerScheduler() {
1211-
1212 }
1213
1214 void EngineWorkerScheduler::bindWorker(EngineWorker* pWorker) {
1215@@ -29,8 +32,10 @@
1216
1217 void EngineWorkerScheduler::workerReady(EngineWorker* pWorker) {
1218 if (pWorker) {
1219- QMutexLocker locker(&m_mutex);
1220- m_scheduledWorkers.insert(pWorker);
1221+ // If the write fails, we really can't do much since we should not block
1222+ // in this slot. Write the address of the variable pWorker, since it is
1223+ // a 1-element array.
1224+ m_scheduleFIFO.write(&pWorker, 1);
1225 }
1226 }
1227
1228@@ -38,16 +43,25 @@
1229 }
1230
1231 void EngineWorkerScheduler::workerFinished(EngineWorker* pWorker) {
1232+ QMutexLocker locker(&m_mutex);
1233+ m_activeWorkers.remove(pWorker);
1234 }
1235
1236-
1237 void EngineWorkerScheduler::runWorkers() {
1238- QMutexLocker locker(&m_mutex);
1239- QMutableSetIterator<EngineWorker*> it(m_scheduledWorkers);
1240- while (it.hasNext()) {
1241- EngineWorker* pWorker = it.next();
1242- it.remove();
1243- start(pWorker);
1244+ m_waitCondition.wakeAll();
1245+}
1246+
1247+void EngineWorkerScheduler::run() {
1248+ m_mutex.lock();
1249+ while (true) {
1250+ EngineWorker* pWorker = NULL;
1251+ while (m_scheduleFIFO.read(&pWorker, 1) == 1) {
1252+ if (pWorker && !m_activeWorkers.contains(pWorker)) {
1253+ m_activeWorkers.insert(pWorker);
1254+ m_workerThreadPool.start(pWorker);
1255+ }
1256+ }
1257+ m_waitCondition.wait(&m_mutex);
1258 }
1259 }
1260
1261
1262=== modified file 'mixxx/src/engine/engineworkerscheduler.h'
1263--- mixxx/src/engine/engineworkerscheduler.h 2010-10-19 01:35:26 +0000
1264+++ mixxx/src/engine/engineworkerscheduler.h 2011-10-04 14:01:27 +0000
1265@@ -1,21 +1,34 @@
1266 #ifndef ENGINEWORKERSCHEDULER_H
1267 #define ENGINEWORKERSCHEDULER_H
1268
1269-#include <QSignalMapper>
1270+#include <QMutex>
1271 #include <QSet>
1272 #include <QThreadPool>
1273-#include <QMutex>
1274+#include <QWaitCondition>
1275+
1276+#include "util/fifo.h"
1277+
1278+// The max engine workers that can be expected to run within a callback
1279+// (e.g. the max that we will schedule). Must be a power of 2.
1280+#define MAX_ENGINE_WORKERS 32
1281+// The max number of threads that EngineWorkers will be scheduled on. TODO(XXX)
1282+// this should be dynamically chosen by the user, since it will vary depending
1283+// on the machine resources available.
1284+#define ENGINE_WORKER_THREAD_COUNT 4
1285
1286 class EngineWorker;
1287
1288-class EngineWorkerScheduler : public QThreadPool {
1289+class EngineWorkerScheduler : public QThread {
1290 Q_OBJECT
1291 public:
1292 EngineWorkerScheduler(QObject* pParent=NULL);
1293 virtual ~EngineWorkerScheduler();
1294
1295+ void bindWorker(EngineWorker* pWorker);
1296 void runWorkers();
1297- void bindWorker(EngineWorker* pWorker);
1298+
1299+ protected:
1300+ void run();
1301
1302 private slots:
1303 void workerReady(EngineWorker* worker);
1304@@ -23,9 +36,11 @@
1305 void workerFinished(EngineWorker* worker);
1306
1307 private:
1308- QSet<EngineWorker*> m_scheduledWorkers;
1309+ FIFO<EngineWorker*> m_scheduleFIFO;
1310+ QThreadPool m_workerThreadPool;
1311+ QWaitCondition m_waitCondition;
1312 QMutex m_mutex;
1313+ QSet<EngineWorker*> m_activeWorkers;
1314 };
1315
1316-
1317 #endif /* ENGINEWORKERSCHEDULER_H */
1318
1319=== modified file 'mixxx/src/engine/readaheadmanager.cpp'
1320--- mixxx/src/engine/readaheadmanager.cpp 2011-04-16 20:54:58 +0000
1321+++ mixxx/src/engine/readaheadmanager.cpp 2011-10-04 14:01:27 +0000
1322@@ -120,19 +120,24 @@
1323 m_iCurrentPosition = iSeekPosition;
1324 }
1325
1326-void ReadAheadManager::hintReader(QList<Hint>& hintList, int iSamplesPerBuffer) {
1327+void ReadAheadManager::hintReader(double dRate, QList<Hint>& hintList,
1328+ int iSamplesPerBuffer) {
1329+ bool in_reverse = dRate < 0;
1330 Hint current_position;
1331
1332- // Make sure that we have enough samples to do n more process() calls
1333- // without reading again either forward or reverse.
1334- int n = 64; // 5? 10? 20? who knows!
1335- int length_to_cache = iSamplesPerBuffer * n;
1336+ // SoundTouch can read up to 2 chunks ahead. Always keep 2 chunks ahead in
1337+ // cache.
1338+ int length_to_cache = 2*CachingReader::kSamplesPerChunk;
1339+
1340 current_position.length = length_to_cache;
1341- current_position.sample = m_iCurrentPosition;
1342+ current_position.sample = in_reverse ?
1343+ m_iCurrentPosition - length_to_cache :
1344+ m_iCurrentPosition;
1345
1346 // If we are trying to cache before the start of the track,
1347 // Then we don't need to cache because it's all zeros!
1348- if (current_position.sample < 0)
1349+ if (current_position.sample < 0 &&
1350+ current_position.sample + current_position.length < 0)
1351 return;
1352
1353 // top priority, we need to read this data immediately
1354
1355=== modified file 'mixxx/src/engine/readaheadmanager.h'
1356--- mixxx/src/engine/readaheadmanager.h 2011-03-13 23:22:35 +0000
1357+++ mixxx/src/engine/readaheadmanager.h 2011-10-04 14:01:27 +0000
1358@@ -46,7 +46,8 @@
1359
1360 // hintReader allows the ReadAheadManager to provide hints to the reader to
1361 // indicate that the given portion of a song is about to be read.
1362- virtual void hintReader(QList<Hint>& hintList, int iSamplesPerBuffer);
1363+ virtual void hintReader(double dRate, QList<Hint>& hintList,
1364+ int iSamplesPerBuffer);
1365
1366 private:
1367 // A broken method for choosing which EngineControl to trust for determining
1368
1369=== added file 'mixxx/src/engine/syncworker.cpp'
1370--- mixxx/src/engine/syncworker.cpp 1970-01-01 00:00:00 +0000
1371+++ mixxx/src/engine/syncworker.cpp 2011-10-04 14:01:27 +0000
1372@@ -0,0 +1,25 @@
1373+#include "engine/syncworker.h"
1374+
1375+#include "controlobject.h"
1376+#include "engine/engineworkerscheduler.h"
1377+
1378+SyncWorker::SyncWorker(EngineWorkerScheduler* pScheduler) {
1379+ pScheduler->bindWorker(this);
1380+}
1381+
1382+SyncWorker::~SyncWorker() {
1383+}
1384+
1385+void SyncWorker::run() {
1386+ // Notify the EngineWorkerScheduler that the work we scheduled is starting.
1387+ emit(workStarting(this));
1388+
1389+ ControlObject::sync();
1390+
1391+ // Notify the EngineWorkerScheduler that the work we did is done.
1392+ emit(workDone(this));
1393+}
1394+
1395+void SyncWorker::schedule() {
1396+ emit(workReady(this));
1397+}
1398
1399=== added file 'mixxx/src/engine/syncworker.h'
1400--- mixxx/src/engine/syncworker.h 1970-01-01 00:00:00 +0000
1401+++ mixxx/src/engine/syncworker.h 2011-10-04 14:01:27 +0000
1402@@ -0,0 +1,20 @@
1403+#ifndef SYNCWORKER_H
1404+#define SYNCWORKER_H
1405+
1406+#include <QObject>
1407+
1408+#include "engine/engineworker.h"
1409+
1410+class EngineWorkerScheduler;
1411+
1412+class SyncWorker : public EngineWorker {
1413+ Q_OBJECT
1414+ public:
1415+ explicit SyncWorker(EngineWorkerScheduler* pScheduler);
1416+ virtual ~SyncWorker();
1417+
1418+ void run();
1419+ void schedule();
1420+};
1421+
1422+#endif /* SYNCWORKER_H */
1423
1424=== modified file 'mixxx/src/engine/vinylcontrolcontrol.cpp'
1425--- mixxx/src/engine/vinylcontrolcontrol.cpp 2011-05-11 04:38:17 +0000
1426+++ mixxx/src/engine/vinylcontrolcontrol.cpp 2011-10-04 14:01:27 +0000
1427@@ -27,12 +27,18 @@
1428 m_pControlVinylEnabled = new ControlPushButton(ConfigKey(pGroup, "vinylcontrol_enabled"));
1429 m_pControlVinylEnabled->set(0);
1430 m_pControlVinylEnabled->setToggleButton(true);
1431+ m_pControlVinylWantEnabled = new ControlPushButton(ConfigKey(pGroup, "vinylcontrol_wantenabled"));
1432+ m_pControlVinylWantEnabled->set(0);
1433+ m_pControlVinylWantEnabled->setToggleButton(true);
1434 m_pControlVinylMode = new ControlPushButton(ConfigKey(pGroup, "vinylcontrol_mode"));
1435 m_pControlVinylMode->setStates(3);
1436 m_pControlVinylMode->setToggleButton(true);
1437 m_pControlVinylCueing = new ControlPushButton(ConfigKey(pGroup, "vinylcontrol_cueing"));
1438 m_pControlVinylCueing->setStates(3);
1439 m_pControlVinylCueing->setToggleButton(true);
1440+ m_pControlVinylSignalEnabled = new ControlPushButton(ConfigKey(pGroup, "vinylcontrol_signal_enabled"));
1441+ m_pControlVinylSignalEnabled->set(1);
1442+ m_pControlVinylSignalEnabled->setToggleButton(true);
1443 }
1444
1445 VinylControlControl::~VinylControlControl() {
1446
1447=== modified file 'mixxx/src/engine/vinylcontrolcontrol.h'
1448--- mixxx/src/engine/vinylcontrolcontrol.h 2011-05-11 04:38:17 +0000
1449+++ mixxx/src/engine/vinylcontrolcontrol.h 2011-10-04 14:01:27 +0000
1450@@ -25,7 +25,9 @@
1451 ControlObject* m_pControlVinylStatus;
1452 ControlPushButton* m_pControlVinylMode;
1453 ControlPushButton* m_pControlVinylEnabled;
1454+ ControlPushButton* m_pControlVinylWantEnabled;
1455 ControlPushButton* m_pControlVinylCueing;
1456+ ControlPushButton* m_pControlVinylSignalEnabled;
1457 TrackPointer m_pCurrentTrack;
1458 };
1459
1460
1461=== modified file 'mixxx/src/library/basesqltablemodel.cpp'
1462--- mixxx/src/library/basesqltablemodel.cpp 2011-09-25 08:26:53 +0000
1463+++ mixxx/src/library/basesqltablemodel.cpp 2011-10-04 14:01:27 +0000
1464@@ -113,6 +113,7 @@
1465 return QAbstractTableModel::headerData(section, orientation, role);
1466 }
1467
1468+<<<<<<< TREE
1469 QString BaseSqlTableModel::orderByClause() const {
1470 bool tableColumnSort = m_iSortColumn < m_tableColumns.size();
1471
1472@@ -134,6 +135,29 @@
1473 QLatin1String(" DESC"));
1474 return s;
1475 }
1476+=======
1477+QString BaseSqlTableModel::orderByClause() const {
1478+ bool tableColumnSort = m_iSortColumn < m_tableColumns.size();
1479+
1480+ if (m_iSortColumn < 0 || !tableColumnSort) {
1481+ return "";
1482+ }
1483+
1484+ QString field = m_idColumn;
1485+ if (m_iSortColumn != 0) {
1486+ field = m_tableColumns[m_iSortColumn];
1487+ }
1488+
1489+ QString s;
1490+ s.append(QLatin1String("ORDER BY "));
1491+ QString sort_field = QString("%1.%2").arg(m_tableName).arg(field);
1492+ s.append(sort_field);
1493+
1494+ s.append((m_eSortOrder == Qt::AscendingOrder) ? QLatin1String(" ASC") :
1495+ QLatin1String(" DESC"));
1496+ return s;
1497+}
1498+>>>>>>> MERGE-SOURCE
1499
1500 void BaseSqlTableModel::select() {
1501 if (!m_bInitialized) {
1502@@ -217,6 +241,7 @@
1503 query.value(tableColumnIndex);
1504 }
1505 rowInfo.push_back(thisRowInfo);
1506+<<<<<<< TREE
1507 }
1508
1509 if (sDebug) {
1510@@ -259,6 +284,53 @@
1511 }
1512 QLinkedList<int>& rows = m_trackIdToRows[row.trackId];
1513 rows.push_back(i);
1514+=======
1515+ }
1516+
1517+ if (sDebug) {
1518+ qDebug() << "Rows actually received:" << rowInfo.size();
1519+ }
1520+
1521+ // Adjust sort column to remove table columns and add 1 to add an id column.
1522+ int sortColumn = m_iSortColumn - m_tableColumns.size() + 1;
1523+
1524+ if (sortColumn < 0) {
1525+ sortColumn = 0;
1526+ }
1527+
1528+ // If we were sorting a table column, then secondary sort by id. TODO(rryan)
1529+ // we should look into being able to drop the secondary sort to save time
1530+ // but going for correctness first.
1531+ m_trackSource->filterAndSort(trackIds, m_currentSearch,
1532+ m_currentSearchFilter,
1533+ sortColumn, m_eSortOrder,
1534+ &m_trackSortOrder);
1535+
1536+ // Only re-sort results if the sort column was a track column.
1537+ if (sortColumn > 0) {
1538+ for (QVector<RowInfo>::iterator it = rowInfo.begin();
1539+ it != rowInfo.end(); ++it) {
1540+ it->order = m_trackSortOrder.value(it->trackId, -1);
1541+ }
1542+
1543+ // RowInfo::operator< sorts by the order field, except -1 is placed at the
1544+ // end so we can easily slice off rows that are no longer present.
1545+ qSort(rowInfo.begin(), rowInfo.end());
1546+ }
1547+
1548+ m_trackIdToRows.clear();
1549+ for (int i = 0; i < rowInfo.size(); ++i) {
1550+ const RowInfo& row = rowInfo[i];
1551+
1552+ if (row.order == -1) {
1553+ // We've reached the end of valid rows. Resize rowInfo to cut off
1554+ // this and all further elements.
1555+ rowInfo.resize(i);
1556+ break;
1557+ }
1558+ QLinkedList<int>& rows = m_trackIdToRows[row.trackId];
1559+ rows.push_back(i);
1560+>>>>>>> MERGE-SOURCE
1561 }
1562
1563 // We're done! Issue the update signals and replace the master maps.
1564@@ -367,6 +439,7 @@
1565 }
1566
1567 int BaseSqlTableModel::columnCount(const QModelIndex& parent) const {
1568+<<<<<<< TREE
1569 if (parent.isValid()) {
1570 return 0;
1571 }
1572@@ -378,6 +451,16 @@
1573 qDebug() << "columnCount()" << parent << count;
1574 }
1575 return count;
1576+=======
1577+ if (parent.isValid()) {
1578+ return 0;
1579+ }
1580+
1581+ // Subtract one from trackSource::columnCount to ignore the id column
1582+ int count = m_tableColumns.size() +
1583+ (m_trackSource ? m_trackSource->columnCount() - 1: 0);
1584+ return count;
1585+>>>>>>> MERGE-SOURCE
1586 }
1587
1588 int BaseSqlTableModel::fieldIndex(const QString& fieldName) const {
1589@@ -426,6 +509,8 @@
1590 // Convert to a bool. Not really that useful since it gets
1591 // converted right back to a QVariant
1592 value = (value == "true") ? true : false;
1593+ } else if (column == fieldIndex(LIBRARYTABLE_DATETIMEADDED)) {
1594+ value = value.toDateTime();
1595 }
1596 break;
1597 case Qt::EditRole:
1598
1599=== modified file 'mixxx/src/library/basetrackcache.cpp'
1600--- mixxx/src/library/basetrackcache.cpp 2011-09-25 06:39:39 +0000
1601+++ mixxx/src/library/basetrackcache.cpp 2011-10-04 14:01:27 +0000
1602@@ -23,9 +23,15 @@
1603 BaseTrackCache::BaseTrackCache(TrackCollection* pTrackCollection,
1604 QString tableName,
1605 QString idColumn,
1606+<<<<<<< TREE
1607 QList<QString> columns,
1608 bool isCaching)
1609 : QObject(NULL),
1610+=======
1611+ QList<QString> columns,
1612+ bool isCaching)
1613+ : QObject(),
1614+>>>>>>> MERGE-SOURCE
1615 m_tableName(tableName),
1616 m_idColumn(idColumn),
1617 m_columns(columns),
1618@@ -40,7 +46,8 @@
1619 << "album"
1620 << "location"
1621 << "comment"
1622- << "title";
1623+ << "title"
1624+ << "genre";
1625
1626 // Convert all the search column names to their field indexes because we use
1627 // them a bunch.
1628@@ -211,7 +218,13 @@
1629 return QVariant(pTrack->getAlbum());
1630 } else if (fieldIndex(LIBRARYTABLE_YEAR) == column) {
1631 return QVariant(pTrack->getYear());
1632- } else if (fieldIndex(LIBRARYTABLE_GENRE) == column) {
1633+<<<<<<< TREE
1634+ } else if (fieldIndex(LIBRARYTABLE_GENRE) == column) {
1635+=======
1636+ } else if (fieldIndex(LIBRARYTABLE_DATETIMEADDED) == column) {
1637+ return QVariant(pTrack->getDateAdded());
1638+ } else if (fieldIndex(LIBRARYTABLE_GENRE) == column) {
1639+>>>>>>> MERGE-SOURCE
1640 return QVariant(pTrack->getGenre());
1641 } else if (fieldIndex(LIBRARYTABLE_FILETYPE) == column) {
1642 return QVariant(pTrack->getType());
1643
1644=== modified file 'mixxx/src/library/dao/trackdao.cpp'
1645--- mixxx/src/library/dao/trackdao.cpp 2011-09-25 05:53:29 +0000
1646+++ mixxx/src/library/dao/trackdao.cpp 2011-10-04 14:01:27 +0000
1647@@ -580,6 +580,7 @@
1648 QString filetype = query.value(query.record().indexOf("filetype")).toString();
1649 QString location = query.value(query.record().indexOf("location")).toString();
1650 bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool();
1651+ QDateTime date_created = query.value(query.record().indexOf("datetime_added")).toDateTime();
1652
1653 TrackInfoObject* track = new TrackInfoObject(location, false);
1654 TrackPointer pTrack = TrackPointer(track, &TrackDAO::deleteTrack);
1655@@ -622,6 +623,7 @@
1656 track->setType(filetype);
1657 track->setLocation(location);
1658 track->setHeaderParsed(header_parsed);
1659+ track->setDateAdded(date_created);
1660
1661 track->setCuePoints(m_cueDao.getCuesForTrack(trackId));
1662 track->setDirty(false);
1663@@ -711,7 +713,7 @@
1664 "filetype, rating, key, track_locations.location as location, "
1665 "track_locations.filesize as filesize, comment, url, duration, bitrate, "
1666 "samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, "
1667- "header_parsed, timesplayed, played, beats_version, beats "
1668+ "header_parsed, timesplayed, played, beats_version, beats, datetime_added "
1669 "FROM Library "
1670 "INNER JOIN track_locations "
1671 "ON library.location = track_locations.id "
1672
1673=== modified file 'mixxx/src/library/itunes/itunesfeature.cpp'
1674--- mixxx/src/library/itunes/itunesfeature.cpp 2011-09-25 04:34:39 +0000
1675+++ mixxx/src/library/itunes/itunesfeature.cpp 2011-10-04 14:01:27 +0000
1676@@ -18,25 +18,50 @@
1677
1678 ITunesFeature::ITunesFeature(QObject* parent, TrackCollection* pTrackCollection)
1679 : LibraryFeature(parent),
1680- m_pTrackCollection(pTrackCollection),
1681- m_database(pTrackCollection->getDatabase()) {
1682- QString tableName = "itunes_library";
1683- QString idColumn = "id";
1684- QStringList columns;
1685- columns << "id"
1686- << "artist"
1687- << "album"
1688- << "genre"
1689- << "location"
1690- << "comment"
1691- << "duration"
1692- << "bitrate"
1693- << "bpm"
1694- << "rating";
1695- pTrackCollection->addTrackSource(
1696- QString("itunes"), QSharedPointer<BaseTrackCache>(
1697- new BaseTrackCache(m_pTrackCollection, tableName, idColumn,
1698- columns, false)));
1699+<<<<<<< TREE
1700+ m_pTrackCollection(pTrackCollection),
1701+ m_database(pTrackCollection->getDatabase()) {
1702+ QString tableName = "itunes_library";
1703+ QString idColumn = "id";
1704+ QStringList columns;
1705+ columns << "id"
1706+ << "artist"
1707+ << "album"
1708+ << "genre"
1709+ << "location"
1710+ << "comment"
1711+ << "duration"
1712+ << "bitrate"
1713+ << "bpm"
1714+ << "rating";
1715+ pTrackCollection->addTrackSource(
1716+ QString("itunes"), QSharedPointer<BaseTrackCache>(
1717+ new BaseTrackCache(m_pTrackCollection, tableName, idColumn,
1718+ columns, false)));
1719+=======
1720+ m_pTrackCollection(pTrackCollection),
1721+ m_database(pTrackCollection->getDatabase()) {
1722+ QString tableName = "itunes_library";
1723+ QString idColumn = "id";
1724+ QStringList columns;
1725+ columns << "id"
1726+ << "artist"
1727+ << "title"
1728+ << "album"
1729+ << "year"
1730+ << "genre"
1731+ << "tracknumber"
1732+ << "location"
1733+ << "comment"
1734+ << "duration"
1735+ << "bitrate"
1736+ << "bpm"
1737+ << "rating";
1738+ pTrackCollection->addTrackSource(
1739+ QString("itunes"), QSharedPointer<BaseTrackCache>(
1740+ new BaseTrackCache(m_pTrackCollection, tableName, idColumn,
1741+ columns, false)));
1742+>>>>>>> MERGE-SOURCE
1743 m_pITunesTrackModel = new ITunesTrackModel(this, m_pTrackCollection);
1744 m_pITunesPlaylistModel = new ITunesPlaylistModel(this, m_pTrackCollection);
1745 m_isActivated = false;
1746@@ -128,7 +153,7 @@
1747 // Let a worker thread do the XML parsing
1748 m_future = QtConcurrent::run(this, &ITunesFeature::importLibrary);
1749 m_future_watcher.setFuture(m_future);
1750- m_title = tr("iTunes (loading)");
1751+ m_title = tr("(loading) iTunes");
1752 //calls a slot in the sidebar model such that 'iTunes (isLoading)' is displayed.
1753 emit (featureIsLoading(this));
1754 }
1755
1756=== modified file 'mixxx/src/library/preparelibrarytablemodel.cpp'
1757--- mixxx/src/library/preparelibrarytablemodel.cpp 2011-09-25 08:08:35 +0000
1758+++ mixxx/src/library/preparelibrarytablemodel.cpp 2011-10-04 14:01:27 +0000
1759@@ -10,7 +10,7 @@
1760 : LibraryTableModel(parent, pTrackCollection,
1761 "mixxx.db.model.prepare") {
1762 m_bShowRecentSongs = true;
1763- slotSearch("");
1764+ setSearch("", m_bShowRecentSongs ? RECENT_FILTER : QString());
1765 select();
1766
1767 connect(this, SIGNAL(doSearch(const QString&)),
1768@@ -22,6 +22,7 @@
1769 }
1770
1771 bool PrepareLibraryTableModel::isColumnInternal(int column) {
1772+<<<<<<< TREE
1773 bool result = false;
1774
1775 if ((column == fieldIndex(LIBRARYTABLE_DATETIMEADDED))) {
1776@@ -31,6 +32,9 @@
1777 }
1778
1779 return result;
1780+=======
1781+ return LibraryTableModel::isColumnInternal(column);
1782+>>>>>>> MERGE-SOURCE
1783 }
1784
1785 void PrepareLibraryTableModel::search(const QString& searchText) {
1786
1787=== modified file 'mixxx/src/library/rhythmbox/rhythmboxfeature.cpp'
1788--- mixxx/src/library/rhythmbox/rhythmboxfeature.cpp 2011-09-25 04:34:39 +0000
1789+++ mixxx/src/library/rhythmbox/rhythmboxfeature.cpp 2011-10-04 14:01:27 +0000
1790@@ -92,7 +92,7 @@
1791 QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4
1792 m_track_future = QtConcurrent::run(this, &RhythmboxFeature::importMusicCollection);
1793 m_track_watcher.setFuture(m_track_future);
1794- m_title = "Rhythmbox (loading)";
1795+ m_title = "(loading) Rhythmbox";
1796 //calls a slot in the sidebar model such that 'Rhythmbox (isLoading)' is displayed.
1797 emit (featureIsLoading(this));
1798 }
1799
1800=== modified file 'mixxx/src/library/traktor/traktorfeature.cpp'
1801--- mixxx/src/library/traktor/traktorfeature.cpp 2011-09-25 04:34:39 +0000
1802+++ mixxx/src/library/traktor/traktorfeature.cpp 2011-10-04 14:01:27 +0000
1803@@ -105,7 +105,7 @@
1804 // Let a worker thread do the XML parsing
1805 m_future = QtConcurrent::run(this, &TraktorFeature::importLibrary, getTraktorMusicDatabase());
1806 m_future_watcher.setFuture(m_future);
1807- m_title = tr("Traktor (loading)");
1808+ m_title = tr("(loading) Traktor");
1809 //calls a slot in the sidebar model such that 'iTunes (isLoading)' is displayed.
1810 emit (featureIsLoading(this));
1811 } else {
1812
1813=== modified file 'mixxx/src/mixxx.cpp'
1814--- mixxx/src/mixxx.cpp 2011-09-25 06:39:39 +0000
1815+++ mixxx/src/mixxx.cpp 2011-10-04 14:01:27 +0000
1816@@ -481,7 +481,8 @@
1817 if (!(m_pWidgetParent = m_pSkinLoader->loadDefaultSkin(m_pView,
1818 m_pKeyboard,
1819 m_pPlayerManager,
1820- m_pLibrary))) {
1821+ m_pLibrary,
1822+ m_pVCManager))) {
1823 qDebug() << "Could not load default skin.";
1824 }
1825
1826@@ -875,9 +876,6 @@
1827 // Either check or uncheck the vinyl control menu item depending on what
1828 // it was saved as.
1829 m_pOptionsVinylControl->setCheckable(true);
1830- //make sure control is off on startup (this is redundant to vinylcontrolmanager.cpp)
1831- m_pConfig->set(
1832- ConfigKey("[VinylControl]", "enabled_ch1"), false);
1833 m_pOptionsVinylControl->setChecked(false);
1834 m_pOptionsVinylControl->setStatusTip(tr("Activate Vinyl Control"));
1835 m_pOptionsVinylControl->setWhatsThis(
1836@@ -891,8 +889,6 @@
1837 SLOT(slotControlVinylControl(double)));
1838
1839 m_pOptionsVinylControl2->setCheckable(true);
1840- m_pConfig->set(
1841- ConfigKey("[VinylControl]", "enabled_ch2"), false);
1842 m_pOptionsVinylControl2->setChecked(false);
1843 m_pOptionsVinylControl2->setStatusTip(tr("Activate Vinyl Control"));
1844 m_pOptionsVinylControl2->setWhatsThis(
1845@@ -1162,15 +1158,9 @@
1846 {
1847 #ifdef __VINYLCONTROL__
1848 if (m_pVCManager->vinylInputEnabled(1)) {
1849- m_pConfig->set(
1850- ConfigKey("[VinylControl]", "enabled_ch1"), ConfigValue((int)toggle));
1851 m_pOptionsVinylControl->setChecked((bool)toggle);
1852- if (toggle) {
1853- ControlObject::getControl(ConfigKey("[Channel1]", "vinylcontrol_status"))->set(VINYL_STATUS_OK);
1854- } else {
1855- ControlObject::getControl(ConfigKey("[Channel1]", "vinylcontrol_status"))->set(VINYL_STATUS_DISABLED);
1856- }
1857 } else {
1858+ m_pOptionsVinylControl->setChecked(false);
1859 if (toggle) {
1860 QMessageBox::warning(this, tr("Mixxx"),
1861 tr("No input device(s) select.\nPlease select your soundcard(s) "
1862@@ -1179,8 +1169,6 @@
1863 QMessageBox::Ok);
1864 m_pPrefDlg->show();
1865 m_pPrefDlg->showSoundHardwarePage();
1866- m_pOptionsVinylControl->setChecked(false);
1867- m_pConfig->set(ConfigKey("[VinylControl]","enabled_ch1"), ConfigValue(0));
1868 ControlObject::getControl(ConfigKey("[Channel1]", "vinylcontrol_status"))->set(VINYL_STATUS_DISABLED);
1869 ControlObject::getControl(ConfigKey("[Channel1]", "vinylcontrol_enabled"))->set(0);
1870 }
1871@@ -1191,27 +1179,17 @@
1872 void MixxxApp::slotCheckboxVinylControl(bool toggle)
1873 {
1874 #ifdef __VINYLCONTROL__
1875- bool current = (bool)m_pConfig->getValueString(ConfigKey("[VinylControl]","enabled_ch1")).toInt();
1876- if (current != toggle) {
1877- ControlObject::getControl(ConfigKey("[Channel1]", "vinylcontrol_enabled"))->set((double)toggle);
1878- }
1879+ ControlObject::getControl(ConfigKey("[Channel1]", "vinylcontrol_enabled"))->set((double)toggle);
1880 #endif
1881 }
1882
1883 void MixxxApp::slotControlVinylControl2(double toggle)
1884 {
1885 #ifdef __VINYLCONTROL__
1886- //we just need at least 1 input (deck 1) because of single deck mode
1887 if (m_pVCManager->vinylInputEnabled(2)) {
1888- m_pConfig->set(
1889- ConfigKey("[VinylControl]", "enabled_ch2"), ConfigValue((int)toggle));
1890- m_pOptionsVinylControl2->setChecked((bool)toggle);
1891- if (toggle) {
1892- ControlObject::getControl(ConfigKey("[Channel2]", "vinylcontrol_status"))->set(VINYL_STATUS_OK);
1893- } else {
1894- ControlObject::getControl(ConfigKey("[Channel2]", "vinylcontrol_status"))->set(VINYL_STATUS_DISABLED);
1895- }
1896+ m_pOptionsVinylControl2->setChecked((bool)toggle);
1897 } else {
1898+ m_pOptionsVinylControl2->setChecked(false);
1899 if (toggle) {
1900 QMessageBox::warning(this, tr("Mixxx"),
1901 tr("No input device(s) select.\nPlease select your soundcard(s) "
1902@@ -1220,8 +1198,6 @@
1903 QMessageBox::Ok);
1904 m_pPrefDlg->show();
1905 m_pPrefDlg->showSoundHardwarePage();
1906- m_pOptionsVinylControl2->setChecked(false);
1907- m_pConfig->set(ConfigKey("[VinylControl]","enabled_ch2"), ConfigValue(0));
1908 ControlObject::getControl(ConfigKey("[Channel2]", "vinylcontrol_status"))->set(VINYL_STATUS_DISABLED);
1909 ControlObject::getControl(ConfigKey("[Channel2]", "vinylcontrol_enabled"))->set(0);
1910 }
1911@@ -1232,10 +1208,7 @@
1912 void MixxxApp::slotCheckboxVinylControl2(bool toggle)
1913 {
1914 #ifdef __VINYLCONTROL__
1915- bool current = (bool)m_pConfig->getValueString(ConfigKey("[VinylControl]","enabled_ch2")).toInt();
1916- if (current != toggle) {
1917- ControlObject::getControl(ConfigKey("[Channel2]", "vinylcontrol_enabled"))->set((double)toggle);
1918- }
1919+ ControlObject::getControl(ConfigKey("[Channel2]", "vinylcontrol_enabled"))->set((double)toggle);
1920 #endif
1921 }
1922
1923@@ -1313,6 +1286,7 @@
1924 "Joe Colosimo<br>"
1925 "Shashank Kumar<br>"
1926 "Till Hofmann<br>"
1927+"Daniel Sch&uuml;rmann<br>"
1928
1929 "</p>"
1930 "<p align=\"center\"><b>And special thanks to:</b></p>"
1931@@ -1428,7 +1402,8 @@
1932 if (!(m_pWidgetParent = m_pSkinLoader->loadDefaultSkin(m_pView,
1933 m_pKeyboard,
1934 m_pPlayerManager,
1935- m_pLibrary))) {
1936+ m_pLibrary,
1937+ m_pVCManager))) {
1938 qDebug() << "Could not reload the skin.";
1939 }
1940
1941
1942=== modified file 'mixxx/src/skin/legacyskinparser.cpp'
1943--- mixxx/src/skin/legacyskinparser.cpp 2011-05-05 21:29:33 +0000
1944+++ mixxx/src/skin/legacyskinparser.cpp 2011-10-04 14:01:27 +0000
1945@@ -58,11 +58,13 @@
1946 LegacySkinParser::LegacySkinParser(ConfigObject<ConfigValue>* pConfig,
1947 MixxxKeyboard* pKeyboard,
1948 PlayerManager* pPlayerManager,
1949- Library* pLibrary)
1950+ Library* pLibrary,
1951+ VinylControlManager* pVCMan)
1952 : m_pConfig(pConfig),
1953 m_pKeyboard(pKeyboard),
1954 m_pPlayerManager(pPlayerManager),
1955 m_pLibrary(pLibrary),
1956+ m_pVCManager(pVCMan),
1957 m_pParent(NULL) {
1958
1959 }
1960@@ -611,7 +613,7 @@
1961 QWidget* LegacySkinParser::parseSpinny(QDomElement node) {
1962 QString channelStr = lookupNodeGroup(node);
1963 const char* pSafeChannelStr = safeChannelString(channelStr);
1964- WSpinny* p = new WSpinny(m_pParent);
1965+ WSpinny* p = new WSpinny(m_pParent, m_pVCManager);
1966 setupWidget(node, p);
1967
1968 connect(p, SIGNAL(trackDropped(QString, QString)),
1969
1970=== modified file 'mixxx/src/skin/legacyskinparser.h'
1971--- mixxx/src/skin/legacyskinparser.h 2011-04-29 03:18:02 +0000
1972+++ mixxx/src/skin/legacyskinparser.h 2011-10-04 14:01:27 +0000
1973@@ -9,6 +9,7 @@
1974
1975 #include "configobject.h"
1976 #include "skin/skinparser.h"
1977+#include "vinylcontrol/vinylcontrolmanager.h"
1978
1979 class Library;
1980 class MixxxKeyboard;
1981@@ -20,7 +21,7 @@
1982 public:
1983 LegacySkinParser(ConfigObject<ConfigValue>* pConfig,
1984 MixxxKeyboard* pKeyboard, PlayerManager* pPlayerManager,
1985- Library* pLibrary);
1986+ Library* pLibrary, VinylControlManager* pVCMan);
1987 virtual ~LegacySkinParser();
1988
1989 virtual bool canParse(QString skinPath);
1990@@ -73,6 +74,7 @@
1991 MixxxKeyboard* m_pKeyboard;
1992 PlayerManager* m_pPlayerManager;
1993 Library* m_pLibrary;
1994+ VinylControlManager* m_pVCManager;
1995 QWidget *m_pParent;
1996 static QList<const char*> s_channelStrs;
1997 static QMutex s_safeStringMutex;
1998
1999=== modified file 'mixxx/src/skin/skinloader.cpp'
2000--- mixxx/src/skin/skinloader.cpp 2011-09-25 06:39:39 +0000
2001+++ mixxx/src/skin/skinloader.cpp 2011-10-04 14:01:27 +0000
2002@@ -5,6 +5,7 @@
2003 #include <QDir>
2004 #include <QtDebug>
2005
2006+#include "vinylcontrol/vinylcontrolmanager.h"
2007 #include "skin/skinloader.h"
2008 #include "skin/legacyskinparser.h"
2009
2010@@ -52,9 +53,14 @@
2011 QWidget* SkinLoader::loadDefaultSkin(QWidget* pParent,
2012 MixxxKeyboard* pKeyboard,
2013 PlayerManager* pPlayerManager,
2014- Library* pLibrary) {
2015+ Library* pLibrary,
2016+ VinylControlManager* pVCMan) {
2017 QString skinPath = getConfiguredSkinPath();
2018
2019+<<<<<<< TREE
2020 LegacySkinParser legacy(m_pConfig, pKeyboard, pPlayerManager, pLibrary);
2021+=======
2022+ LegacySkinParser legacy(m_pConfig, pKeyboard, pPlayerManager, pLibrary, pVCMan);
2023+>>>>>>> MERGE-SOURCE
2024 return legacy.parseSkin(skinPath, pParent);
2025 }
2026
2027=== modified file 'mixxx/src/skin/skinloader.h'
2028--- mixxx/src/skin/skinloader.h 2011-01-06 19:05:52 +0000
2029+++ mixxx/src/skin/skinloader.h 2011-10-04 14:01:27 +0000
2030@@ -17,7 +17,8 @@
2031 QWidget* loadDefaultSkin(QWidget* pParent,
2032 MixxxKeyboard* pKeyboard,
2033 PlayerManager* pPlayerManager,
2034- Library* pLibrary);
2035+ Library* pLibrary,
2036+ VinylControlManager* pVCMan);
2037
2038 QString getConfiguredSkinPath();
2039
2040
2041=== modified file 'mixxx/src/soundmanager.cpp'
2042--- mixxx/src/soundmanager.cpp 2011-09-25 02:17:38 +0000
2043+++ mixxx/src/soundmanager.cpp 2011-10-04 14:01:27 +0000
2044@@ -26,6 +26,7 @@
2045 #include "sounddevice.h"
2046 #include "sounddeviceportaudio.h"
2047 #include "engine/enginemaster.h"
2048+#include "engine/enginebuffer.h"
2049 #include "controlobjectthreadmain.h"
2050 #include "soundmanagerutil.h"
2051 #include "controlobject.h"
2052@@ -496,13 +497,6 @@
2053 // latency checks itself for validity on SMConfig::setLatency()
2054 }
2055
2056-void SoundManager::sync()
2057-{
2058- ControlObject::sync();
2059- //qDebug() << "sync";
2060-
2061-}
2062-
2063 //Requests a buffer in the proper format, if we're prepared to give one.
2064 QHash<AudioOutput, const CSAMPLE*>
2065 SoundManager::requestBuffer(QList<AudioOutput> outputs,
2066@@ -546,8 +540,6 @@
2067 {
2068 // Only generate a new buffer for the clock reference card
2069 // qDebug() << "New buffer for" << device->getDisplayName() << "of size" << iFramesPerBuffer;
2070- //First, sync control parameters with changes from GUI thread
2071- sync();
2072
2073 //Process a block of samples for output. iFramesPerBuffer is the
2074 //number of samples for one channel, but the EngineObject
2075
2076=== modified file 'mixxx/src/soundmanager.h'
2077--- mixxx/src/soundmanager.h 2011-09-25 02:17:38 +0000
2078+++ mixxx/src/soundmanager.h 2011-10-04 14:01:27 +0000
2079@@ -68,8 +68,6 @@
2080 void devicesSetup(); // emitted when the sound devices have been set up
2081 void outputRegistered(AudioOutput output, const AudioSource *src);
2082 void inputRegistered(AudioInput input, AudioDestination *dest);
2083-public slots:
2084- void sync();
2085 private:
2086 void clearOperativeVariables();
2087 void setJACKName() const;
2088
2089=== modified file 'mixxx/src/track/beatfactory.cpp'
2090--- mixxx/src/track/beatfactory.cpp 2011-02-26 07:08:21 +0000
2091+++ mixxx/src/track/beatfactory.cpp 2011-10-04 14:01:27 +0000
2092@@ -4,7 +4,8 @@
2093 #include "track/beatmatrix.h"
2094 #include "track/beatfactory.h"
2095
2096-BeatsPointer BeatFactory::loadBeatsFromByteArray(TrackPointer pTrack, QString beatsVersion,
2097+BeatsPointer BeatFactory::loadBeatsFromByteArray(TrackPointer pTrack,
2098+ QString beatsVersion,
2099 QByteArray* beatsSerialized) {
2100 // TODO(XXX) make it so the version strings are not in multiple places
2101 if (beatsVersion == "BeatGrid-1.0") {
2102@@ -31,13 +32,11 @@
2103 }
2104
2105 void BeatFactory::deleteBeats(Beats* pBeats) {
2106- // This assumes all Beats* variants multiply-inherit from QObject. Kind of ugly. Oh well.
2107+ // This assumes all Beats* variants multiply-inherit from QObject. Kind of
2108+ // ugly. Oh well.
2109 QObject* pObject = dynamic_cast<QObject*>(pBeats);
2110
2111 if (pObject != NULL) {
2112- qDebug() << "Deleting QObject instance.";
2113 pObject->deleteLater();
2114- } else {
2115- qDebug() << "Could not delete Beats instance, did not dynamic_cast to QObject";
2116 }
2117 }
2118
2119=== modified file 'mixxx/src/trackinfoobject.cpp'
2120--- mixxx/src/trackinfoobject.cpp 2011-08-21 02:24:54 +0000
2121+++ mixxx/src/trackinfoobject.cpp 2011-10-04 14:01:27 +0000
2122@@ -133,7 +133,7 @@
2123 m_iChannels = 0;
2124 m_fCuePoint = 0.0f;
2125 m_dVisualResampleRate = 0;
2126- m_dCreateDate = QDateTime::currentDateTime();
2127+ m_dCreateDate = m_dateAdded = QDateTime::currentDateTime();
2128 m_Rating = 0;
2129 m_key = "";
2130
2131@@ -403,6 +403,16 @@
2132 return sInfo;
2133 }
2134
2135+QDateTime TrackInfoObject::getDateAdded() const {
2136+ QMutexLocker lock(&m_qMutex);
2137+ return m_dateAdded;
2138+}
2139+
2140+void TrackInfoObject::setDateAdded(QDateTime dateAdded) {
2141+ QMutexLocker lock(&m_qMutex);
2142+ m_dateAdded = dateAdded;
2143+}
2144+
2145 int TrackInfoObject::getDuration() const
2146 {
2147 QMutexLocker lock(&m_qMutex);
2148
2149=== modified file 'mixxx/src/trackinfoobject.h'
2150--- mixxx/src/trackinfoobject.h 2011-03-29 12:58:50 +0000
2151+++ mixxx/src/trackinfoobject.h 2011-10-04 14:01:27 +0000
2152@@ -148,6 +148,8 @@
2153 /** Output a formatted string with all the info */
2154 QString getInfo() const;
2155
2156+ QDateTime getDateAdded() const;
2157+ void setDateAdded(QDateTime dateAdded);
2158
2159 /** Getter/Setter methods for metadata */
2160 /** Return title */
2161@@ -360,6 +362,8 @@
2162 float m_fCuePoint;
2163 /** Date. creation date of file */
2164 QDateTime m_dCreateDate;
2165+ // Date the track was added to the library
2166+ QDateTime m_dateAdded;
2167
2168 QString m_key;
2169
2170
2171=== added directory 'mixxx/src/util'
2172=== added file 'mixxx/src/util/fifo.h'
2173--- mixxx/src/util/fifo.h 1970-01-01 00:00:00 +0000
2174+++ mixxx/src/util/fifo.h 2011-10-04 14:01:27 +0000
2175@@ -0,0 +1,44 @@
2176+#ifndef FIFO_H
2177+#define FIFO_H
2178+
2179+#include "util/pa_ringbuffer.h"
2180+#include "util.h"
2181+
2182+template <class DataType>
2183+class FIFO {
2184+ public:
2185+ explicit FIFO(int size) {
2186+ m_data = new DataType[size];
2187+ Q_ASSERT(PaUtil_InitializeRingBuffer(
2188+ &m_ringBuffer, sizeof(DataType), size, m_data) == 0);
2189+ }
2190+ virtual ~FIFO() {
2191+ delete [] m_data;
2192+ }
2193+ int readAvailable() const {
2194+ return PaUtil_GetRingBufferReadAvailable(&m_ringBuffer);
2195+ }
2196+ int writeAvailable() const {
2197+ return PaUtil_GetRingBufferWriteAvailable(&m_ringBuffer);
2198+ }
2199+ int read(DataType* pData, int count) {
2200+ return PaUtil_ReadRingBuffer(&m_ringBuffer, pData, count);
2201+ }
2202+ int write(const DataType* pData, int count) {
2203+ return PaUtil_WriteRingBuffer(&m_ringBuffer, pData, count);
2204+ }
2205+ void writeBlocking(const DataType* pData, int count) {
2206+ int written = 0;
2207+ while (written != count) {
2208+ int i = write(pData, count);
2209+ pData += i;
2210+ written += i;
2211+ }
2212+ }
2213+ private:
2214+ DataType* m_data;
2215+ PaUtilRingBuffer m_ringBuffer;
2216+ DISALLOW_COPY_AND_ASSIGN(FIFO<DataType>);
2217+};
2218+
2219+#endif /* FIFO_H */
2220
2221=== added file 'mixxx/src/util/pa_memorybarrier.h'
2222--- mixxx/src/util/pa_memorybarrier.h 1970-01-01 00:00:00 +0000
2223+++ mixxx/src/util/pa_memorybarrier.h 2011-10-04 14:01:27 +0000
2224@@ -0,0 +1,128 @@
2225+/*
2226+ * $Id: pa_memorybarrier.h 1240 2007-07-17 13:05:07Z bjornroche $
2227+ * Portable Audio I/O Library
2228+ * Memory barrier utilities
2229+ *
2230+ * Author: Bjorn Roche, XO Audio, LLC
2231+ *
2232+ * This program uses the PortAudio Portable Audio Library.
2233+ * For more information see: http://www.portaudio.com
2234+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
2235+ *
2236+ * Permission is hereby granted, free of charge, to any person obtaining
2237+ * a copy of this software and associated documentation files
2238+ * (the "Software"), to deal in the Software without restriction,
2239+ * including without limitation the rights to use, copy, modify, merge,
2240+ * publish, distribute, sublicense, and/or sell copies of the Software,
2241+ * and to permit persons to whom the Software is furnished to do so,
2242+ * subject to the following conditions:
2243+ *
2244+ * The above copyright notice and this permission notice shall be
2245+ * included in all copies or substantial portions of the Software.
2246+ *
2247+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2248+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2249+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2250+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
2251+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
2252+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2253+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2254+ */
2255+
2256+/*
2257+ * The text above constitutes the entire PortAudio license; however,
2258+ * the PortAudio community also makes the following non-binding requests:
2259+ *
2260+ * Any person wishing to distribute modifications to the Software is
2261+ * requested to send the modifications to the original developer so that
2262+ * they can be incorporated into the canonical version. It is also
2263+ * requested that these non-binding requests be included along with the
2264+ * license above.
2265+ */
2266+
2267+/**
2268+ @file pa_memorybarrier.h
2269+ @ingroup common_src
2270+*/
2271+
2272+/****************
2273+ * Some memory barrier primitives based on the system.
2274+ * right now only OS X, FreeBSD, and Linux are supported. In addition to providing
2275+ * memory barriers, these functions should ensure that data cached in registers
2276+ * is written out to cache where it can be snooped by other CPUs. (ie, the volatile
2277+ * keyword should not be required)
2278+ *
2279+ * the primitives that must be defined are:
2280+ *
2281+ * PaUtil_FullMemoryBarrier()
2282+ * PaUtil_ReadMemoryBarrier()
2283+ * PaUtil_WriteMemoryBarrier()
2284+ *
2285+ ****************/
2286+
2287+#if defined(__APPLE__)
2288+# include <libkern/OSAtomic.h>
2289+ /* Here are the memory barrier functions. Mac OS X only provides
2290+ full memory barriers, so the three types of barriers are the same,
2291+ however, these barriers are superior to compiler-based ones. */
2292+# define PaUtil_FullMemoryBarrier() OSMemoryBarrier()
2293+# define PaUtil_ReadMemoryBarrier() OSMemoryBarrier()
2294+# define PaUtil_WriteMemoryBarrier() OSMemoryBarrier()
2295+#elif defined(__GNUC__)
2296+ /* GCC >= 4.1 has built-in intrinsics. We'll use those */
2297+# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)
2298+# define PaUtil_FullMemoryBarrier() __sync_synchronize()
2299+# define PaUtil_ReadMemoryBarrier() __sync_synchronize()
2300+# define PaUtil_WriteMemoryBarrier() __sync_synchronize()
2301+ /* as a fallback, GCC understands volatile asm and "memory" to mean it
2302+ * should not reorder memory read/writes */
2303+ /* Note that it is not clear that any compiler actually defines __PPC__,
2304+ * it can probably removed safely. */
2305+# elif defined( __ppc__ ) || defined( __powerpc__) || defined( __PPC__ )
2306+# define PaUtil_FullMemoryBarrier() asm volatile("sync":::"memory")
2307+# define PaUtil_ReadMemoryBarrier() asm volatile("sync":::"memory")
2308+# define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory")
2309+# elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || \
2310+ defined( __i686__ ) || defined( __x86_64__ )
2311+# define PaUtil_FullMemoryBarrier() asm volatile("mfence":::"memory")
2312+# define PaUtil_ReadMemoryBarrier() asm volatile("lfence":::"memory")
2313+# define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory")
2314+# else
2315+# ifdef ALLOW_SMP_DANGERS
2316+# warning Memory barriers not defined on this system or system unknown
2317+# warning For SMP safety, you should fix this.
2318+# define PaUtil_FullMemoryBarrier()
2319+# define PaUtil_ReadMemoryBarrier()
2320+# define PaUtil_WriteMemoryBarrier()
2321+# else
2322+# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed.
2323+# endif
2324+# endif
2325+#elif (_MSC_VER >= 1400) && !defined(_WIN32_WCE)
2326+# include <intrin.h>
2327+# pragma intrinsic(_ReadWriteBarrier)
2328+# pragma intrinsic(_ReadBarrier)
2329+# pragma intrinsic(_WriteBarrier)
2330+/* note that MSVC intrinsics _ReadWriteBarrier(), _ReadBarrier(), _WriteBarrier() are just compiler barriers *not* memory barriers */
2331+# define PaUtil_FullMemoryBarrier() _ReadWriteBarrier()
2332+# define PaUtil_ReadMemoryBarrier() _ReadBarrier()
2333+# define PaUtil_WriteMemoryBarrier() _WriteBarrier()
2334+#elif defined(_WIN32_WCE)
2335+# define PaUtil_FullMemoryBarrier()
2336+# define PaUtil_ReadMemoryBarrier()
2337+# define PaUtil_WriteMemoryBarrier()
2338+#elif defined(_MSC_VER) || defined(__BORLANDC__)
2339+# define PaUtil_FullMemoryBarrier() _asm { lock add [esp], 0 }
2340+# define PaUtil_ReadMemoryBarrier() _asm { lock add [esp], 0 }
2341+# define PaUtil_WriteMemoryBarrier() _asm { lock add [esp], 0 }
2342+#else
2343+# ifdef ALLOW_SMP_DANGERS
2344+# warning Memory barriers not defined on this system or system unknown
2345+# warning For SMP safety, you should fix this.
2346+# define PaUtil_FullMemoryBarrier()
2347+# define PaUtil_ReadMemoryBarrier()
2348+# define PaUtil_WriteMemoryBarrier()
2349+# else
2350+# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed.
2351+# endif
2352+#endif
2353
2354=== added file 'mixxx/src/util/pa_ringbuffer.c'
2355--- mixxx/src/util/pa_ringbuffer.c 1970-01-01 00:00:00 +0000
2356+++ mixxx/src/util/pa_ringbuffer.c 2011-10-04 14:01:27 +0000
2357@@ -0,0 +1,238 @@
2358+/*
2359+ * $Id: pa_ringbuffer.c 1738 2011-08-18 11:47:28Z rossb $
2360+ * Portable Audio I/O Library
2361+ * Ring Buffer utility.
2362+ *
2363+ * Author: Phil Burk, http://www.softsynth.com
2364+ * modified for SMP safety on Mac OS X by Bjorn Roche
2365+ * modified for SMP safety on Linux by Leland Lucius
2366+ * also, allowed for const where possible
2367+ * modified for multiple-byte-sized data elements by Sven Fischer
2368+ *
2369+ * Note that this is safe only for a single-thread reader and a
2370+ * single-thread writer.
2371+ *
2372+ * This program uses the PortAudio Portable Audio Library.
2373+ * For more information see: http://www.portaudio.com
2374+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
2375+ *
2376+ * Permission is hereby granted, free of charge, to any person obtaining
2377+ * a copy of this software and associated documentation files
2378+ * (the "Software"), to deal in the Software without restriction,
2379+ * including without limitation the rights to use, copy, modify, merge,
2380+ * publish, distribute, sublicense, and/or sell copies of the Software,
2381+ * and to permit persons to whom the Software is furnished to do so,
2382+ * subject to the following conditions:
2383+ *
2384+ * The above copyright notice and this permission notice shall be
2385+ * included in all copies or substantial portions of the Software.
2386+ *
2387+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2388+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2389+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2390+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
2391+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
2392+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2393+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2394+ */
2395+
2396+/*
2397+ * The text above constitutes the entire PortAudio license; however,
2398+ * the PortAudio community also makes the following non-binding requests:
2399+ *
2400+ * Any person wishing to distribute modifications to the Software is
2401+ * requested to send the modifications to the original developer so that
2402+ * they can be incorporated into the canonical version. It is also
2403+ * requested that these non-binding requests be included along with the
2404+ * license above.
2405+ */
2406+
2407+/**
2408+ @file
2409+ @ingroup common_src
2410+*/
2411+
2412+#include <stdio.h>
2413+#include <stdlib.h>
2414+#include <math.h>
2415+#include <string.h>
2416+
2417+#include "util/pa_memorybarrier.h"
2418+#include "util/pa_ringbuffer.h"
2419+
2420+/***************************************************************************
2421+ * Initialize FIFO.
2422+ * elementCount must be power of 2, returns -1 if not.
2423+ */
2424+ring_buffer_size_t PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void *dataPtr )
2425+{
2426+ if( ((elementCount-1) & elementCount) != 0) return -1; /* Not Power of two. */
2427+ rbuf->bufferSize = elementCount;
2428+ rbuf->buffer = (char *)dataPtr;
2429+ PaUtil_FlushRingBuffer( rbuf );
2430+ rbuf->bigMask = (elementCount*2)-1;
2431+ rbuf->smallMask = (elementCount)-1;
2432+ rbuf->elementSizeBytes = elementSizeBytes;
2433+ return 0;
2434+}
2435+
2436+/***************************************************************************
2437+** Return number of elements available for reading. */
2438+ring_buffer_size_t PaUtil_GetRingBufferReadAvailable( const PaUtilRingBuffer *rbuf )
2439+{
2440+ return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask );
2441+}
2442+/***************************************************************************
2443+** Return number of elements available for writing. */
2444+ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable( const PaUtilRingBuffer *rbuf )
2445+{
2446+ return ( rbuf->bufferSize - PaUtil_GetRingBufferReadAvailable(rbuf));
2447+}
2448+
2449+/***************************************************************************
2450+** Clear buffer. Should only be called when buffer is NOT being read or written. */
2451+void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf )
2452+{
2453+ rbuf->writeIndex = rbuf->readIndex = 0;
2454+}
2455+
2456+/***************************************************************************
2457+** Get address of region(s) to which we can write data.
2458+** If the region is contiguous, size2 will be zero.
2459+** If non-contiguous, size2 will be the size of second region.
2460+** Returns room available to be written or elementCount, whichever is smaller.
2461+*/
2462+ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount,
2463+ void **dataPtr1, ring_buffer_size_t *sizePtr1,
2464+ void **dataPtr2, ring_buffer_size_t *sizePtr2 )
2465+{
2466+ ring_buffer_size_t index;
2467+ ring_buffer_size_t available = PaUtil_GetRingBufferWriteAvailable( rbuf );
2468+ if( elementCount > available ) elementCount = available;
2469+ /* Check to see if write is not contiguous. */
2470+ index = rbuf->writeIndex & rbuf->smallMask;
2471+ if( (index + elementCount) > rbuf->bufferSize )
2472+ {
2473+ /* Write data in two blocks that wrap the buffer. */
2474+ ring_buffer_size_t firstHalf = rbuf->bufferSize - index;
2475+ *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes];
2476+ *sizePtr1 = firstHalf;
2477+ *dataPtr2 = &rbuf->buffer[0];
2478+ *sizePtr2 = elementCount - firstHalf;
2479+ }
2480+ else
2481+ {
2482+ *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes];
2483+ *sizePtr1 = elementCount;
2484+ *dataPtr2 = NULL;
2485+ *sizePtr2 = 0;
2486+ }
2487+
2488+ if( available )
2489+ PaUtil_FullMemoryBarrier(); /* (write-after-read) => full barrier */
2490+
2491+ return elementCount;
2492+}
2493+
2494+
2495+/***************************************************************************
2496+*/
2497+ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount )
2498+{
2499+ /* ensure that previous writes are seen before we update the write index
2500+ (write after write)
2501+ */
2502+ PaUtil_WriteMemoryBarrier();
2503+ return rbuf->writeIndex = (rbuf->writeIndex + elementCount) & rbuf->bigMask;
2504+}
2505+
2506+/***************************************************************************
2507+** Get address of region(s) from which we can read data.
2508+** If the region is contiguous, size2 will be zero.
2509+** If non-contiguous, size2 will be the size of second region.
2510+** Returns room available to be read or elementCount, whichever is smaller.
2511+*/
2512+ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount,
2513+ void **dataPtr1, ring_buffer_size_t *sizePtr1,
2514+ void **dataPtr2, ring_buffer_size_t *sizePtr2 )
2515+{
2516+ ring_buffer_size_t index;
2517+ ring_buffer_size_t available = PaUtil_GetRingBufferReadAvailable( rbuf ); /* doesn't use memory barrier */
2518+ if( elementCount > available ) elementCount = available;
2519+ /* Check to see if read is not contiguous. */
2520+ index = rbuf->readIndex & rbuf->smallMask;
2521+ if( (index + elementCount) > rbuf->bufferSize )
2522+ {
2523+ /* Write data in two blocks that wrap the buffer. */
2524+ ring_buffer_size_t firstHalf = rbuf->bufferSize - index;
2525+ *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes];
2526+ *sizePtr1 = firstHalf;
2527+ *dataPtr2 = &rbuf->buffer[0];
2528+ *sizePtr2 = elementCount - firstHalf;
2529+ }
2530+ else
2531+ {
2532+ *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes];
2533+ *sizePtr1 = elementCount;
2534+ *dataPtr2 = NULL;
2535+ *sizePtr2 = 0;
2536+ }
2537+
2538+ if( available )
2539+ PaUtil_ReadMemoryBarrier(); /* (read-after-read) => read barrier */
2540+
2541+ return elementCount;
2542+}
2543+/***************************************************************************
2544+*/
2545+ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount )
2546+{
2547+ /* ensure that previous reads (copies out of the ring buffer) are always completed before updating (writing) the read index.
2548+ (write-after-read) => full barrier
2549+ */
2550+ PaUtil_FullMemoryBarrier();
2551+ return rbuf->readIndex = (rbuf->readIndex + elementCount) & rbuf->bigMask;
2552+}
2553+
2554+/***************************************************************************
2555+** Return elements written. */
2556+ring_buffer_size_t PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, ring_buffer_size_t elementCount )
2557+{
2558+ ring_buffer_size_t size1, size2, numWritten;
2559+ void *data1, *data2;
2560+ numWritten = PaUtil_GetRingBufferWriteRegions( rbuf, elementCount, &data1, &size1, &data2, &size2 );
2561+ if( size2 > 0 )
2562+ {
2563+
2564+ memcpy( data1, data, size1*rbuf->elementSizeBytes );
2565+ data = ((char *)data) + size1*rbuf->elementSizeBytes;
2566+ memcpy( data2, data, size2*rbuf->elementSizeBytes );
2567+ }
2568+ else
2569+ {
2570+ memcpy( data1, data, size1*rbuf->elementSizeBytes );
2571+ }
2572+ PaUtil_AdvanceRingBufferWriteIndex( rbuf, numWritten );
2573+ return numWritten;
2574+}
2575+
2576+/***************************************************************************
2577+** Return elements read. */
2578+ring_buffer_size_t PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, ring_buffer_size_t elementCount )
2579+{
2580+ ring_buffer_size_t size1, size2, numRead;
2581+ void *data1, *data2;
2582+ numRead = PaUtil_GetRingBufferReadRegions( rbuf, elementCount, &data1, &size1, &data2, &size2 );
2583+ if( size2 > 0 )
2584+ {
2585+ memcpy( data, data1, size1*rbuf->elementSizeBytes );
2586+ data = ((char *)data) + size1*rbuf->elementSizeBytes;
2587+ memcpy( data, data2, size2*rbuf->elementSizeBytes );
2588+ }
2589+ else
2590+ {
2591+ memcpy( data, data1, size1*rbuf->elementSizeBytes );
2592+ }
2593+ PaUtil_AdvanceRingBufferReadIndex( rbuf, numRead );
2594+ return numRead;
2595+}
2596
2597=== added file 'mixxx/src/util/pa_ringbuffer.h'
2598--- mixxx/src/util/pa_ringbuffer.h 1970-01-01 00:00:00 +0000
2599+++ mixxx/src/util/pa_ringbuffer.h 2011-10-04 14:01:27 +0000
2600@@ -0,0 +1,233 @@
2601+#ifndef PA_RINGBUFFER_H
2602+#define PA_RINGBUFFER_H
2603+/*
2604+ * $Id: pa_ringbuffer.h 1734 2011-08-18 11:19:36Z rossb $
2605+ * Portable Audio I/O Library
2606+ * Ring Buffer utility.
2607+ *
2608+ * Author: Phil Burk, http://www.softsynth.com
2609+ * modified for SMP safety on OS X by Bjorn Roche.
2610+ * also allowed for const where possible.
2611+ * modified for multiple-byte-sized data elements by Sven Fischer
2612+ *
2613+ * Note that this is safe only for a single-thread reader
2614+ * and a single-thread writer.
2615+ *
2616+ * This program is distributed with the PortAudio Portable Audio Library.
2617+ * For more information see: http://www.portaudio.com
2618+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
2619+ *
2620+ * Permission is hereby granted, free of charge, to any person obtaining
2621+ * a copy of this software and associated documentation files
2622+ * (the "Software"), to deal in the Software without restriction,
2623+ * including without limitation the rights to use, copy, modify, merge,
2624+ * publish, distribute, sublicense, and/or sell copies of the Software,
2625+ * and to permit persons to whom the Software is furnished to do so,
2626+ * subject to the following conditions:
2627+ *
2628+ * The above copyright notice and this permission notice shall be
2629+ * included in all copies or substantial portions of the Software.
2630+ *
2631+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2632+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2633+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2634+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
2635+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
2636+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2637+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2638+ */
2639+
2640+/*
2641+ * The text above constitutes the entire PortAudio license; however,
2642+ * the PortAudio community also makes the following non-binding requests:
2643+ *
2644+ * Any person wishing to distribute modifications to the Software is
2645+ * requested to send the modifications to the original developer so that
2646+ * they can be incorporated into the canonical version. It is also
2647+ * requested that these non-binding requests be included along with the
2648+ * license above.
2649+ */
2650+
2651+/** @file
2652+ @ingroup common_src
2653+ @brief Single-reader single-writer lock-free ring buffer
2654+
2655+ PaUtilRingBuffer is a ring buffer used to transport samples between
2656+ different execution contexts (threads, OS callbacks, interrupt handlers)
2657+ without requiring the use of any locks. This only works when there is
2658+ a single reader and a single writer (ie. one thread or callback writes
2659+ to the ring buffer, another thread or callback reads from it).
2660+
2661+ The PaUtilRingBuffer structure manages a ring buffer containing N
2662+ elements, where N must be a power of two. An element may be any size
2663+ (specified in bytes).
2664+
2665+ The memory area used to store the buffer elements must be allocated by
2666+ the client prior to calling PaUtil_InitializeRingBuffer() and must outlive
2667+ the use of the ring buffer.
2668+*/
2669+
2670+#if defined(__APPLE__)
2671+#include <sys/types.h>
2672+typedef int32_t ring_buffer_size_t;
2673+#elif defined( __GNUC__ )
2674+typedef long ring_buffer_size_t;
2675+#elif (_MSC_VER >= 1400)
2676+typedef long ring_buffer_size_t;
2677+#elif defined(_MSC_VER) || defined(__BORLANDC__)
2678+typedef long ring_buffer_size_t;
2679+#else
2680+typedef long ring_buffer_size_t;
2681+#endif
2682+
2683+
2684+
2685+#ifdef __cplusplus
2686+extern "C"
2687+{
2688+#endif /* __cplusplus */
2689+
2690+typedef struct PaUtilRingBuffer
2691+{
2692+ ring_buffer_size_t bufferSize; /**< Number of elements in FIFO. Power of 2. Set by PaUtil_InitRingBuffer. */
2693+ volatile ring_buffer_size_t writeIndex; /**< Index of next writable element. Set by PaUtil_AdvanceRingBufferWriteIndex. */
2694+ volatile ring_buffer_size_t readIndex; /**< Index of next readable element. Set by PaUtil_AdvanceRingBufferReadIndex. */
2695+ ring_buffer_size_t bigMask; /**< Used for wrapping indices with extra bit to distinguish full/empty. */
2696+ ring_buffer_size_t smallMask; /**< Used for fitting indices to buffer. */
2697+ ring_buffer_size_t elementSizeBytes; /**< Number of bytes per element. */
2698+ char *buffer; /**< Pointer to the buffer containing the actual data. */
2699+}PaUtilRingBuffer;
2700+
2701+/** Initialize Ring Buffer to empty state ready to have elements written to it.
2702+
2703+ @param rbuf The ring buffer.
2704+
2705+ @param elementSizeBytes The size of a single data element in bytes.
2706+
2707+ @param elementCount The number of elements in the buffer (must be a power of 2).
2708+
2709+ @param dataPtr A pointer to a previously allocated area where the data
2710+ will be maintained. It must be elementCount*elementSizeBytes long.
2711+
2712+ @return -1 if elementCount is not a power of 2, otherwise 0.
2713+*/
2714+ring_buffer_size_t PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void *dataPtr );
2715+
2716+/** Reset buffer to empty. Should only be called when buffer is NOT being read or written.
2717+
2718+ @param rbuf The ring buffer.
2719+*/
2720+void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf );
2721+
2722+/** Retrieve the number of elements available in the ring buffer for writing.
2723+
2724+ @param rbuf The ring buffer.
2725+
2726+ @return The number of elements available for writing.
2727+*/
2728+ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable( const PaUtilRingBuffer *rbuf );
2729+
2730+/** Retrieve the number of elements available in the ring buffer for reading.
2731+
2732+ @param rbuf The ring buffer.
2733+
2734+ @return The number of elements available for reading.
2735+*/
2736+ring_buffer_size_t PaUtil_GetRingBufferReadAvailable( const PaUtilRingBuffer *rbuf );
2737+
2738+/** Write data to the ring buffer.
2739+
2740+ @param rbuf The ring buffer.
2741+
2742+ @param data The address of new data to write to the buffer.
2743+
2744+ @param elementCount The number of elements to be written.
2745+
2746+ @return The number of elements written.
2747+*/
2748+ring_buffer_size_t PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, ring_buffer_size_t elementCount );
2749+
2750+/** Read data from the ring buffer.
2751+
2752+ @param rbuf The ring buffer.
2753+
2754+ @param data The address where the data should be stored.
2755+
2756+ @param elementCount The number of elements to be read.
2757+
2758+ @return The number of elements read.
2759+*/
2760+ring_buffer_size_t PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, ring_buffer_size_t elementCount );
2761+
2762+/** Get address of region(s) to which we can write data.
2763+
2764+ @param rbuf The ring buffer.
2765+
2766+ @param elementCount The number of elements desired.
2767+
2768+ @param dataPtr1 The address where the first (or only) region pointer will be
2769+ stored.
2770+
2771+ @param sizePtr1 The address where the first (or only) region length will be
2772+ stored.
2773+
2774+ @param dataPtr2 The address where the second region pointer will be stored if
2775+ the first region is too small to satisfy elementCount.
2776+
2777+ @param sizePtr2 The address where the second region length will be stored if
2778+ the first region is too small to satisfy elementCount.
2779+
2780+ @return The room available to be written or elementCount, whichever is smaller.
2781+*/
2782+ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount,
2783+ void **dataPtr1, ring_buffer_size_t *sizePtr1,
2784+ void **dataPtr2, ring_buffer_size_t *sizePtr2 );
2785+
2786+/** Advance the write index to the next location to be written.
2787+
2788+ @param rbuf The ring buffer.
2789+
2790+ @param elementCount The number of elements to advance.
2791+
2792+ @return The new position.
2793+*/
2794+ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount );
2795+
2796+/** Get address of region(s) from which we can read data.
2797+
2798+ @param rbuf The ring buffer.
2799+
2800+ @param elementCount The number of elements desired.
2801+
2802+ @param dataPtr1 The address where the first (or only) region pointer will be
2803+ stored.
2804+
2805+ @param sizePtr1 The address where the first (or only) region length will be
2806+ stored.
2807+
2808+ @param dataPtr2 The address where the second region pointer will be stored if
2809+ the first region is too small to satisfy elementCount.
2810+
2811+ @param sizePtr2 The address where the second region length will be stored if
2812+ the first region is too small to satisfy elementCount.
2813+
2814+ @return The number of elements available for reading.
2815+*/
2816+ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount,
2817+ void **dataPtr1, ring_buffer_size_t *sizePtr1,
2818+ void **dataPtr2, ring_buffer_size_t *sizePtr2 );
2819+
2820+/** Advance the read index to the next location to be read.
2821+
2822+ @param rbuf The ring buffer.
2823+
2824+ @param elementCount The number of elements to advance.
2825+
2826+ @return The new position.
2827+*/
2828+ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount );
2829+
2830+#ifdef __cplusplus
2831+}
2832+#endif /* __cplusplus */
2833+#endif /* PA_RINGBUFFER_H */
2834
2835=== modified file 'mixxx/src/vinylcontrol/vinylcontrol.cpp'
2836--- mixxx/src/vinylcontrol/vinylcontrol.cpp 2011-04-29 16:57:44 +0000
2837+++ mixxx/src/vinylcontrol/vinylcontrol.cpp 2011-10-04 14:01:27 +0000
2838@@ -11,6 +11,8 @@
2839
2840 // Get Control objects
2841 playPos = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "playposition"))); //Range: -.14 to 1.14
2842+ trackSamples = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "track_samples")));
2843+ trackSampleRate = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "track_samplerate")));
2844 vinylSeek = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "vinylcontrol_seek")));
2845 controlScratch = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "scratch2")));
2846 rateSlider = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "rate"))); //Range -1.0 to 1.0
2847@@ -19,11 +21,13 @@
2848 duration = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "duration")));
2849 mode = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "vinylcontrol_mode")));
2850 enabled = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "vinylcontrol_enabled")));
2851+ wantenabled = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "vinylcontrol_wantenabled")));
2852 cueing = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "vinylcontrol_cueing")));
2853 rateRange = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "rateRange")));
2854 vinylStatus = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "vinylcontrol_status")));
2855 rateDir = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "rate_dir")));
2856 loopEnabled = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "loop_enabled")));
2857+ signalenabled = new ControlObjectThread(ControlObject::getControl(ConfigKey(group, "vinylcontrol_signal_enabled")));
2858
2859 dVinylPitch = 0.0f;
2860 dVinylPosition = 0.0f;
2861@@ -47,8 +51,8 @@
2862 //Vinyl control mode
2863 iVCMode = m_pConfig->getValueString(ConfigKey("[VinylControl]","mode")).toInt();
2864
2865- //Enabled or not
2866- bIsEnabled = m_pConfig->getValueString(ConfigKey(group,"vinylcontrol_enabled")).toInt();
2867+ //Enabled or not -- load from saved value in case vinyl control is restarting
2868+ bIsEnabled = wantenabled->get();
2869
2870 //Gain
2871 ControlObject::getControl(ConfigKey("[VinylControl]", "gain"))->set(
2872@@ -59,7 +63,9 @@
2873 {
2874 bIsEnabled = enable;
2875 if (m_pConfig)
2876+ {
2877 m_pConfig->set(ConfigKey(m_group,"vinylcontrol_enabled"), ConfigValue((int)enable));
2878+ }
2879
2880 enabled->slotSet(enable);
2881
2882@@ -71,8 +77,15 @@
2883
2884 VinylControl::~VinylControl()
2885 {
2886+ bool wasEnabled = bIsEnabled;
2887 enabled->slotSet(false);
2888 vinylStatus->slotSet(VINYL_STATUS_DISABLED);
2889+ if (wasEnabled)
2890+ {
2891+ //if vinyl control is just restarting, indicate that it should
2892+ //be enabled
2893+ wantenabled->slotSet(true);
2894+ }
2895 }
2896
2897 float VinylControl::getSpeed()
2898
2899=== modified file 'mixxx/src/vinylcontrol/vinylcontrol.h'
2900--- mixxx/src/vinylcontrol/vinylcontrol.h 2011-05-11 04:38:17 +0000
2901+++ mixxx/src/vinylcontrol/vinylcontrol.h 2011-10-04 14:01:27 +0000
2902@@ -35,7 +35,7 @@
2903
2904 #define MIXXX_VC_DEFAULT_LEADINTIME 0
2905
2906-#define MIXXX_VINYL_SCOPE_SIZE 128
2907+#define MIXXX_VINYL_SCOPE_SIZE 100
2908
2909 //TODO: Make this an EngineObject instead one day? (need to route all the input audio through the engine that way too...)
2910
2911@@ -62,6 +62,8 @@
2912 QString m_group;
2913 ControlObjectThread *playButton; //The ControlObject used to start/stop playback of the song.
2914 ControlObjectThread *playPos; //The ControlObject used to read the playback position in the song.
2915+ ControlObjectThread *trackSamples;
2916+ ControlObjectThread *trackSampleRate;
2917 ControlObjectThread *vinylSeek; //The ControlObject used to change the playback position in the song.
2918 ControlObjectThread *controlScratch; //The ControlObject used to seek when the record is spinning fast.
2919 ControlObjectThread *rateSlider; //The ControlObject used to change the speed/pitch of the song.
2920@@ -69,11 +71,13 @@
2921 ControlObjectThread *duration; //The ControlObject used to get the duration of the current song.
2922 ControlObjectThread *mode; //The ControlObject used to get the vinyl control mode (absolute/relative/scratch)
2923 ControlObjectThread *enabled; //The ControlObject used to get if the vinyl control is enabled or disabled.
2924+ ControlObjectThread *wantenabled; //The ControlObject used to get if the vinyl control should try to enable itself
2925 ControlObjectThread *cueing; //Should cueing mode be active?
2926 ControlObjectThread *rateRange; //The ControlObject used to the get the pitch range from the prefs.
2927 ControlObjectThread *vinylStatus;
2928 ControlObjectThread *rateDir; //direction of rate
2929 ControlObjectThread *loopEnabled; //looping enabled?
2930+ ControlObjectThread *signalenabled; //show the signal in the skin?
2931 //ControlObject *vinylStatus; //Status of vinyl control
2932
2933 int iLeadInTime; //The lead-in time...
2934@@ -86,6 +90,7 @@
2935 float fRateRange; //The pitch range setting from Mixxx's preferences
2936 float m_fTimecodeQuality; //Used as a measure of the quality of the timecode signal.
2937
2938+ float fTrackDuration;
2939 unsigned long iSampleRate;
2940 bool bIsEnabled;
2941 int iRIAACorrection;
2942
2943=== modified file 'mixxx/src/vinylcontrol/vinylcontrolmanager.cpp'
2944--- mixxx/src/vinylcontrol/vinylcontrolmanager.cpp 2011-07-19 20:54:56 +0000
2945+++ mixxx/src/vinylcontrol/vinylcontrolmanager.cpp 2011-10-04 14:01:27 +0000
2946@@ -26,11 +26,9 @@
2947 {
2948 // load a bunch of stuff
2949 ControlObject::getControl(ConfigKey("[Channel1]","vinylcontrol_enabled"))
2950- ->queueFromThread(m_pConfig->getValueString(
2951- ConfigKey("[VinylControl]","enabled_ch1")).toDouble());
2952+ ->queueFromThread(0);
2953 ControlObject::getControl(ConfigKey("[Channel2]","vinylcontrol_enabled"))
2954- ->queueFromThread(m_pConfig->getValueString(
2955- ConfigKey("[VinylControl]","enabled_ch2")).toDouble());
2956+ ->queueFromThread(0);
2957 ControlObject::getControl(ConfigKey("[Channel1]","vinylcontrol_mode"))
2958 ->queueFromThread(m_pConfig->getValueString(
2959 ConfigKey("[VinylControl]","mode")).toDouble());
2960@@ -62,8 +60,6 @@
2961
2962 // save a bunch of stuff to config
2963 // turn off vinyl control so it won't be enabled on load (this is redundant to mixxx.cpp)
2964- m_pConfig->set(ConfigKey("[VinylControl]","enabled_ch1"), false);
2965- m_pConfig->set(ConfigKey("[VinylControl]","enabled_ch2"), false);
2966 m_pConfig->set(ConfigKey("[Channel 1]","vinylcontrol_enabled"), false);
2967 m_pConfig->set(ConfigKey("[Channel 2]","vinylcontrol_enabled"), false);
2968 m_pConfig->set(ConfigKey("[VinylControl]","mode"),
2969@@ -151,6 +147,21 @@
2970 return ret;
2971 }
2972
2973+VinylControlProxy* VinylControlManager::getVinylControlProxyForChannel(QString channel)
2974+{
2975+ // TODO: will need update for n-deck
2976+ if (channel == "[Channel1]")
2977+ {
2978+ return m_proxies.at(0);
2979+ }
2980+ else if (channel == "[Channel2]")
2981+ {
2982+ return m_proxies.at(1);
2983+ }
2984+
2985+ return NULL;
2986+}
2987+
2988 void VinylControlManager::toggleDeck(double value) {
2989 if (!value) return;
2990 /** few different cases here:
2991
2992=== modified file 'mixxx/src/vinylcontrol/vinylcontrolmanager.h'
2993--- mixxx/src/vinylcontrol/vinylcontrolmanager.h 2011-07-19 20:23:46 +0000
2994+++ mixxx/src/vinylcontrol/vinylcontrolmanager.h 2011-10-04 14:01:27 +0000
2995@@ -25,6 +25,7 @@
2996 virtual void onInputDisconnected(AudioInput input);
2997 QList<VinylControlProxy*> vinylControlProxies();
2998 bool vinylInputEnabled(int deck);
2999+ VinylControlProxy* getVinylControlProxyForChannel(QString);
3000 public slots:
3001 void reloadConfig();
3002 signals:
3003
3004=== modified file 'mixxx/src/vinylcontrol/vinylcontrolsignalwidget.cpp'
3005--- mixxx/src/vinylcontrol/vinylcontrolsignalwidget.cpp 2011-04-17 04:16:00 +0000
3006+++ mixxx/src/vinylcontrol/vinylcontrolsignalwidget.cpp 2011-10-04 14:01:27 +0000
3007@@ -31,7 +31,7 @@
3008 : QWidget(),
3009 m_iTimerId(0),
3010 m_pVinylControl(NULL),
3011- m_iSize(128),
3012+ m_iSize(MIXXX_VINYL_SCOPE_SIZE),
3013 m_qImage(),
3014 m_bVinylActive(FALSE) {
3015 }
3016
3017=== modified file 'mixxx/src/vinylcontrol/vinylcontrolxwax.cpp'
3018--- mixxx/src/vinylcontrol/vinylcontrolxwax.cpp 2011-05-21 01:07:25 +0000
3019+++ mixxx/src/vinylcontrol/vinylcontrolxwax.cpp 2011-10-04 14:01:27 +0000
3020@@ -47,6 +47,7 @@
3021 iOldMode = MIXXX_VCMODE_ABSOLUTE;
3022 dUiUpdateTime = -1.0f;
3023 m_bNeedleSkipPrevention = (bool)(m_pConfig->getValueString( ConfigKey( "[VinylControl]", "needle_skip_prevention" ) ).toInt());
3024+ signalenabled->slotSet(m_pConfig->getValueString( ConfigKey( "[VinylControl]", "show_signal_quality" ) ).toInt());
3025
3026 dLastTrackSelectPos = 0.0;
3027 dCurTrackSelectPos = 0.0;
3028@@ -97,6 +98,7 @@
3029 //Note that timecoder_init will not double-malloc the LUTs, and after this we are guaranteed
3030 //that the LUT has been generated unless we ran out of memory.
3031 m_bLUTInitialized = true;
3032+ m_uiSafeZone = timecoder_get_safe(&timecoder);
3033 //}
3034 s_xwaxLUTMutex.unlock();
3035
3036@@ -177,6 +179,7 @@
3037 double dPitchRing[RING_SIZE];
3038 int ringPos = 0;
3039 int ringFilled = 0;
3040+ double cur_duration = -1.0f;
3041 double old_duration = -1.0f;
3042 int reportedMode = 0;
3043 bool reportedPlayButton = 0;
3044@@ -225,36 +228,62 @@
3045 //if no track loaded, let track selection work but that's it
3046 if (duration == NULL)
3047 {
3048- bTrackSelectMode = true;
3049- doTrackSelection(false, dVinylPitch, iPosition);
3050+ //until I can figure out how to detect "track 2" on serato CD,
3051+ //don't try track selection
3052+ if (!m_bCDControl)
3053+ {
3054+ bTrackSelectMode = true;
3055+ doTrackSelection(false, dVinylPitch, iPosition);
3056+ }
3057 continue;
3058 }
3059 //qDebug() << m_group << id << iPosition << dVinylPitch;
3060
3061- double cur_duration = duration->get();
3062+ cur_duration = duration->get();
3063+
3064+
3065+ //Has a new track been loaded?
3066 //FIXME? we should really sync on all track changes
3067 if (cur_duration != old_duration)
3068 {
3069 bForceResync=true;
3070 bTrackSelectMode = false; //just in case
3071 old_duration = cur_duration;
3072+ //duration from the control object is an integer. We need
3073+ //more precision:
3074+ fTrackDuration = trackSamples->get() / 2 / trackSampleRate->get();
3075+
3076+ //we were at record end, so turn it off and restore mode
3077+ if(atRecordEnd)
3078+ {
3079+ disableRecordEndMode();
3080+ if (iOldMode == MIXXX_VCMODE_CONSTANT)
3081+ iVCMode = MIXXX_VCMODE_RELATIVE;
3082+ else
3083+ iVCMode = iOldMode;
3084+ }
3085 }
3086
3087- dVinylPosition = iPosition;
3088- dVinylPosition = dVinylPosition / 1000.0f;
3089- dVinylPosition -= iLeadInTime;
3090+ //make sure dVinylPosition only has good values
3091+ if (iPosition != -1)
3092+ {
3093+ dVinylPosition = iPosition;
3094+ dVinylPosition = dVinylPosition / 1000.0f;
3095+ dVinylPosition -= iLeadInTime;
3096+ }
3097+
3098+
3099
3100 //Initialize drift control to zero in case we don't get any position data to calculate it with.
3101 dDriftControl = 0.0f;
3102
3103- filePosition = playPos->get() * cur_duration; //Get the playback position in the file in seconds.
3104+ filePosition = playPos->get() * fTrackDuration; //Get the playback position in the file in seconds.
3105
3106 reportedMode = mode->get();
3107 reportedPlayButton = playButton->get();
3108
3109 if (iVCMode != reportedMode)
3110 {
3111- //qDebug() << "cur mode" << iVCMode << "new mode" << reportedMode;
3112 //if we are playing, don't allow change
3113 //to absolute mode (would cause sudden track skip)
3114 if (reportedPlayButton && reportedMode == MIXXX_VCMODE_ABSOLUTE)
3115@@ -282,7 +311,6 @@
3116 iVCMode = MIXXX_VCMODE_RELATIVE;
3117 mode->slotSet((double)iVCMode);
3118 }
3119-
3120 //are we newly playing near the end of the record? (in absolute mode, this happens
3121 //when the filepos is past safe (more accurate),
3122 //but it can also happen in relative mode if the vinylpos is nearing the end
3123@@ -292,13 +320,13 @@
3124 {
3125 if (iVCMode == MIXXX_VCMODE_ABSOLUTE)
3126 {
3127- if ((filePosition + iLeadInTime) * 1000.0f > timecoder_get_safe(&timecoder) &&
3128+ if ((filePosition + iLeadInTime) * 1000.0f > m_uiSafeZone &&
3129 !bForceResync) //corner case: we are waiting for resync so don't enable just yet
3130 enableRecordEndMode();
3131 }
3132 else if (iVCMode == MIXXX_VCMODE_RELATIVE || iVCMode == MIXXX_VCMODE_CONSTANT)
3133 {
3134- if (iPosition != -1 && iPosition > timecoder_get_safe(&timecoder))
3135+ if (iPosition != -1 && iPosition > m_uiSafeZone)
3136 enableRecordEndMode();
3137 }
3138 }
3139@@ -307,25 +335,20 @@
3140 {
3141 //if atRecordEnd was true, maybe it no longer applies:
3142
3143- if ((iVCMode == MIXXX_VCMODE_ABSOLUTE &&
3144- (filePosition + iLeadInTime) * 1000.0f <= timecoder_get_safe(&timecoder)))
3145- {
3146- //if we are in absolute mode and the file position is in a safe zone now
3147- disableRecordEndMode();
3148- }
3149- else if (!reportedPlayButton)
3150+ if (!reportedPlayButton)
3151 {
3152 //if we turned off play button, also disable
3153 disableRecordEndMode();
3154 }
3155- else if (iPosition != -1)
3156+ else if (iPosition != -1 &&
3157+ iPosition <= m_uiSafeZone &&
3158+ dVinylPosition > 0 &&
3159+ checkSteadyPitch(dVinylPitch, filePosition) > 0.5)
3160+
3161 {
3162- //if relative mode, and vinylpos is safe
3163- if (iVCMode == MIXXX_VCMODE_RELATIVE &&
3164- iPosition <= timecoder_get_safe(&timecoder))
3165- {
3166- disableRecordEndMode();
3167- }
3168+ //if good position, and safe, and not in leadin, and steady,
3169+ //disable
3170+ disableRecordEndMode();
3171 }
3172
3173 if (atRecordEnd)
3174@@ -345,17 +368,22 @@
3175
3176 if (!atRecordEnd)
3177 {
3178- if (iPosition != -1 && iPosition > timecoder_get_safe(&timecoder))
3179+ if (iPosition != -1 && iPosition > m_uiSafeZone)
3180 {
3181- if (!bTrackSelectMode)
3182+ //until I can figure out how to detect "track 2" on serato CD,
3183+ //don't try track selection
3184+ if (!m_bCDControl)
3185 {
3186- qDebug() << "position greater than safe, select mode" << iPosition << timecoder_get_safe(&timecoder);
3187- bTrackSelectMode = true;
3188- togglePlayButton(FALSE);
3189- resetSteadyPitch(0.0f, 0.0f);
3190- controlScratch->slotSet(0.0f);
3191+ if (!bTrackSelectMode)
3192+ {
3193+ qDebug() << "position greater than safe, select mode" << iPosition << m_uiSafeZone;
3194+ bTrackSelectMode = true;
3195+ togglePlayButton(FALSE);
3196+ resetSteadyPitch(0.0f, 0.0f);
3197+ controlScratch->slotSet(0.0f);
3198+ }
3199+ doTrackSelection(true, dVinylPitch, iPosition);
3200 }
3201- doTrackSelection(true, dVinylPitch, iPosition);
3202
3203 //hm I wonder if track will keep playing while this happens?
3204 //not sure what we want to do here... probably enforce
3205@@ -420,20 +448,18 @@
3206
3207 //We have pitch, but not position. so okay signal but not great (scratching / cueing?)
3208 //qDebug() << "Pitch" << dVinylPitch;
3209+
3210 if (iPosition != -1)
3211 {
3212 //POSITION: YES PITCH: YES
3213 //add a value to the pitch ring (for averaging / smoothing the pitch)
3214 //qDebug() << fabs(((dVinylPosition - dOldPos) * (dVinylPitch / fabs(dVinylPitch))));
3215
3216- dPitchRing[ringPos] = dVinylPitch;
3217- if(ringFilled < RING_SIZE)
3218- ringFilled++;
3219-
3220 //save the absolute amount of drift for when we need to estimate vinyl position
3221 dDriftAmt = dVinylPosition - filePosition;
3222+
3223+ //qDebug() << "drift" << dDriftAmt;
3224
3225- //qDebug() << "vinyl" << dVinylPosition << "file" << filePosition;
3226 if (bForceResync)
3227 {
3228 //if forceresync was set but we're no longer absolute,
3229@@ -504,12 +530,13 @@
3230
3231 //Calculate how much the vinyl's position has drifted from it's timecode and compensate for it.
3232 //(This is caused by the manufacturing process of the vinyl.)
3233- dDriftControl = ((filePosition - dVinylPosition) / dVinylPosition) / 100 * 4.0f;
3234+ if (fabs(dDriftAmt) > 0.1 && fabs(dDriftAmt) < 5.0) {
3235+ dDriftControl = dDriftAmt;
3236+ } else {
3237+ dDriftControl = 0.0;
3238+ }
3239
3240 //if we hit the end of the ring, loop around
3241- ringPos++;
3242- if(ringPos >= RING_SIZE)
3243- ringPos = 0;
3244 dOldPos = dVinylPosition;
3245 }
3246 else
3247@@ -528,6 +555,14 @@
3248 ringFilled = 0;
3249 continue;
3250 }
3251+
3252+ if (iVCMode == MIXXX_VCMODE_ABSOLUTE &&
3253+ fabs(dVinylPitch) < 0.05 &&
3254+ fabs(dDriftAmt) >= 0.3f)
3255+ {
3256+ qDebug() << "slow, out of sync, syncing position";
3257+ syncPosition();
3258+ }
3259
3260 dOldPos = filePosition + dDriftAmt;
3261
3262@@ -539,6 +574,21 @@
3263
3264 //playbutton status may have changed
3265 reportedPlayButton = playButton->get();
3266+
3267+ if (reportedPlayButton)
3268+ {
3269+ //only add to the ring if pitch is stable
3270+ dPitchRing[ringPos] = dVinylPitch;
3271+ if(ringFilled < RING_SIZE)
3272+ ringFilled++;
3273+ ringPos = (ringPos + 1) % RING_SIZE;
3274+ }
3275+ else
3276+ {
3277+ //reset ring if pitch isn't steady
3278+ ringPos = 0;
3279+ ringFilled = 0;
3280+ }
3281
3282 //only smooth when we have good position (no smoothing for scratching)
3283 double averagePitch = 0.0f;
3284@@ -556,13 +606,12 @@
3285 else
3286 averagePitch = dVinylPitch;
3287
3288-
3289 if (iVCMode == MIXXX_VCMODE_ABSOLUTE)
3290 {
3291- controlScratch->slotSet(averagePitch + dDriftControl);
3292+ controlScratch->slotSet(dVinylPitch + dDriftControl);
3293 if (iPosition != -1 && reportedPlayButton && uiUpdateTime(filePosition))
3294 {
3295- rateSlider->slotSet(rateDir->get() * (fabs(averagePitch + dDriftControl) - 1.0f) / fRateRange);
3296+ rateSlider->slotSet(rateDir->get() * (fabs(dVinylPitch + dDriftControl) - 1.0f) / fRateRange);
3297 dUiUpdateTime = filePosition;
3298 }
3299 }
3300@@ -587,14 +636,21 @@
3301 //let the track play a wee bit more before deciding we've stopped
3302
3303 rateSlider->slotSet(0.0f);
3304+
3305+ if (iVCMode == MIXXX_VCMODE_ABSOLUTE &&
3306+ fabs(dVinylPosition - filePosition) >= 0.1f)
3307+ {
3308+ qDebug() << "stopped, out of sync, syncing position";
3309+ syncPosition();
3310+ }
3311
3312 if(fabs(filePosition - dOldFilePos) >= 0.1 ||
3313 !m_bNeedleSkipPrevention ||
3314 filePosition == dOldFilePos)
3315 {
3316 //We are not playing any more
3317- togglePlayButton(FALSE);
3318- resetSteadyPitch(0.0f, 0.0f);
3319+ togglePlayButton(FALSE);
3320+ resetSteadyPitch(0.0f, 0.0f);
3321 controlScratch->slotSet(0.0f);
3322 //resetSteadyPitch(dVinylPitch, filePosition);
3323 //Notify the UI that the timecode quality is garbage/missing.
3324@@ -643,15 +699,8 @@
3325 {
3326 vinylStatus->slotSet(VINYL_STATUS_OK);
3327 atRecordEnd = false;
3328- //don't start a new track with constant mode
3329- if (iVCMode == MIXXX_VCMODE_CONSTANT)
3330- {
3331- if (iOldMode == MIXXX_VCMODE_CONSTANT)
3332- iVCMode = MIXXX_VCMODE_RELATIVE;
3333- else
3334- iVCMode = iOldMode;
3335- mode->slotSet((double)iVCMode);
3336- }
3337+ iVCMode = MIXXX_VCMODE_RELATIVE;
3338+ mode->slotSet((double)iVCMode);
3339 }
3340
3341 void VinylControlXwax::togglePlayButton(bool on)
3342@@ -769,11 +818,19 @@
3343 void VinylControlXwax::syncPosition()
3344 {
3345 //qDebug() << "sync position" << dVinylPosition / duration->get();
3346- vinylSeek->slotSet(dVinylPosition / duration->get()); //VinylPos in seconds / total length of song
3347+ vinylSeek->slotSet(dVinylPosition / fTrackDuration); //VinylPos in seconds / total length of song
3348 }
3349
3350 bool VinylControlXwax::checkEnabled(bool was, bool is)
3351 {
3352+ // if we're not enabled, but the last object was, try turning ourselves on
3353+ // XXX: is this just a race that's working right now?
3354+ if (!is and wantenabled->get())
3355+ {
3356+ enabled->slotSet(true);
3357+ wantenabled->slotSet(false); //don't try to do this over and over
3358+ return true; //optimism!
3359+ }
3360 if (was != is)
3361 {
3362 //we reset the scratch value, but we don't reset the rate slider.
3363@@ -824,7 +881,9 @@
3364 {
3365 bQualityRing[iQualPos] = quality_sample;
3366 if(iQualFilled < QUALITY_RING_SIZE)
3367- iQualFilled++;
3368+ {
3369+ iQualFilled++;
3370+ }
3371
3372 int quality = 0;
3373 for (int i=0; i<iQualFilled; i++)
3374@@ -836,9 +895,7 @@
3375 //qDebug() << "quality" << m_fTimecodeQuality;
3376 m_fTimecodeQuality = (float)quality / (float)iQualFilled;
3377
3378- iQualPos++;
3379- if(iQualPos >= QUALITY_RING_SIZE)
3380- iQualPos = 0;
3381+ iQualPos = (iQualPos + 1) % QUALITY_RING_SIZE;
3382 }
3383
3384 float VinylControlXwax::getAngle()
3385
3386=== modified file 'mixxx/src/vinylcontrol/vinylcontrolxwax.h'
3387--- mixxx/src/vinylcontrol/vinylcontrolxwax.h 2011-04-17 04:16:00 +0000
3388+++ mixxx/src/vinylcontrol/vinylcontrolxwax.h 2011-10-04 14:01:27 +0000
3389@@ -15,7 +15,7 @@
3390
3391 #define XWAX_DEVICE_FRAME 32
3392 #define XWAX_SMOOTHING (128 / XWAX_DEVICE_FRAME) /* result value is in frames */
3393-#define RING_SIZE 40
3394+#define RING_SIZE 30
3395 #define QUALITY_RING_SIZE 100
3396 #define MIN_SIGNAL 75
3397
3398@@ -48,6 +48,7 @@
3399 void establishQuality(bool quality_sample);
3400
3401 double dFileLength; // The length (in samples) of the current song.
3402+ unsigned int m_uiSafeZone; // Cache the position of the end of record
3403
3404 double dOldPos; // The position read last time it was polled.
3405 double dOldPitch;
3406
3407=== modified file 'mixxx/src/waveform/waveformrenderer.cpp'
3408--- mixxx/src/waveform/waveformrenderer.cpp 2011-04-06 21:20:35 +0000
3409+++ mixxx/src/waveform/waveformrenderer.cpp 2011-10-04 14:01:27 +0000
3410@@ -26,6 +26,8 @@
3411 #define DEFAULT_SUBPIXELS_PER_PIXEL 4
3412 #define DEFAULT_PIXELS_PER_SECOND 100
3413
3414+#define RATE_INCREMENT 0.015
3415+
3416 void WaveformRenderer::run() {
3417 double msecs_old = 0, msecs_elapsed = 0;
3418
3419@@ -61,6 +63,7 @@
3420 m_dRate(0),
3421 m_dRateRange(0),
3422 m_dRateDir(0),
3423+ m_iRateAdjusting(0),
3424 m_iDupes(0),
3425 m_dPlayPosAdjust(0),
3426 m_iLatency(0),
3427@@ -190,7 +193,7 @@
3428 }
3429
3430 void WaveformRenderer::slotUpdateRate(double v) {
3431- m_dRate = v;
3432+ m_dTargetRate = v;
3433 }
3434
3435 void WaveformRenderer::slotUpdateRateRange(double v) {
3436@@ -501,6 +504,26 @@
3437
3438 //qDebug() << m_dPlayPosAdjust;
3439
3440+ // Gradually stretch the waveform
3441+ if (fabs(m_dTargetRate - m_dRate) > RATE_INCREMENT)
3442+ {
3443+ if ((m_dTargetRate - m_dRate) > 0)
3444+ {
3445+ m_iRateAdjusting = m_iRateAdjusting > 0 ? m_iRateAdjusting + 1 : 1;
3446+ m_dRate = math_min(m_dTargetRate, m_dRate + RATE_INCREMENT * pow(m_iRateAdjusting, 2) / 80);
3447+ }
3448+ else
3449+ {
3450+ m_iRateAdjusting = m_iRateAdjusting < 0 ? m_iRateAdjusting - 1 : -1;
3451+ m_dRate = math_max(m_dTargetRate, m_dRate - RATE_INCREMENT * pow(m_iRateAdjusting, 2) / 80);
3452+ }
3453+ }
3454+ else
3455+ {
3456+ m_iRateAdjusting = 0;
3457+ m_dRate = m_dTargetRate;
3458+ }
3459+
3460 // Limit our rate adjustment to < 99%, "Bad Things" might happen otherwise.
3461 double rateAdjust = m_dRateDir * math_min(0.99, m_dRate * m_dRateRange);
3462
3463
3464=== modified file 'mixxx/src/waveform/waveformrenderer.h'
3465--- mixxx/src/waveform/waveformrenderer.h 2011-03-08 22:17:41 +0000
3466+++ mixxx/src/waveform/waveformrenderer.h 2011-10-04 14:01:27 +0000
3467@@ -61,7 +61,8 @@
3468
3469 int m_iPlayPosTime, m_iPlayPosTimeOld;
3470 QTime m_playPosTime, m_playPosTimeOld;
3471- double m_dPlayPos, m_dPlayPosOld, m_dRate, m_dRateRange, m_dRateDir;
3472+ double m_dPlayPos, m_dPlayPosOld, m_dTargetRate, m_dRate, m_dRateRange, m_dRateDir;
3473+ int m_iRateAdjusting;
3474 int m_iDupes;
3475 double m_dPlayPosAdjust;
3476 int m_iLatency;
3477
3478=== modified file 'mixxx/src/widget/wspinny.cpp'
3479--- mixxx/src/widget/wspinny.cpp 2011-05-12 14:46:18 +0000
3480+++ mixxx/src/widget/wspinny.cpp 2011-10-04 14:01:27 +0000
3481@@ -6,7 +6,7 @@
3482 #include "sharedglcontext.h"
3483 #include "wspinny.h"
3484
3485-WSpinny::WSpinny(QWidget* parent) : QGLWidget(SharedGLContext::getContext(), parent),
3486+WSpinny::WSpinny(QWidget* parent, VinylControlManager* pVCMan) : QGLWidget(SharedGLContext::getContext(), parent),
3487 m_pBG(NULL),
3488 m_pFG(NULL),
3489 m_pGhost(NULL),
3490@@ -20,6 +20,12 @@
3491 m_pScratchToggle(NULL),
3492 m_pScratchPos(NULL),
3493 m_pVinylControlSpeedType(NULL),
3494+ m_pVinylControlEnabled(NULL),
3495+ m_bVinylActive(false),
3496+ m_bSignalActive(true),
3497+ m_iSize(0),
3498+ m_iTimerId(0),
3499+ m_iSignalUpdateTick(0),
3500 m_fAngle(0.0f),
3501 m_fGhostAngle(0.0f),
3502 m_dPausedPosition(0.0f),
3503@@ -29,6 +35,10 @@
3504 m_iFullRotations(0),
3505 m_dPrevTheta(0.)
3506 {
3507+#ifdef __VINYLCONTROL__
3508+ m_pVCManager = pVCMan;
3509+ m_pVinylControl = NULL;
3510+#endif
3511 //Drag and drop
3512 setAcceptDrops(true);
3513 }
3514@@ -69,6 +79,13 @@
3515 if (m_pBG && !m_pBG->isNull()) {
3516 setFixedSize(m_pBG->size());
3517 }
3518+
3519+#ifdef __VINYLCONTROL__
3520+ m_iSize = MIXXX_VINYL_SCOPE_SIZE;
3521+ m_qImage = QImage(m_iSize, m_iSize, QImage::Format_ARGB32);
3522+ //fill with transparent black
3523+ m_qImage.fill(qRgba(0,0,0,0));
3524+#endif
3525
3526 m_pPlay = new ControlObjectThreadMain(ControlObject::getControl(
3527 ConfigKey(group, "play")));
3528@@ -92,6 +109,15 @@
3529 ConfigKey(group, "scratch_position_enable")));
3530 m_pScratchPos = new ControlObjectThreadMain(ControlObject::getControl(
3531 ConfigKey(group, "scratch_position")));
3532+
3533+ Q_ASSERT(m_pPlayPos);
3534+ Q_ASSERT(m_pDuration);
3535+
3536+ //Repaint when visual_playposition changes.
3537+ connect(m_pVisualPlayPos, SIGNAL(valueChanged(double)),
3538+ this, SLOT(updateAngle(double)));
3539+
3540+#ifdef __VINYLCONTROL__
3541 m_pVinylControlSpeedType = new ControlObjectThreadMain(ControlObject::getControl(
3542 ConfigKey(group, "vinylcontrol_speed_type")));
3543 if (m_pVinylControlSpeedType)
3544@@ -99,17 +125,26 @@
3545 //Initialize the rotational speed.
3546 this->updateVinylControlSpeed(m_pVinylControlSpeedType->get());
3547 }
3548- Q_ASSERT(m_pPlayPos);
3549- Q_ASSERT(m_pDuration);
3550-
3551- //Repaint when visual_playposition changes.
3552- connect(m_pVisualPlayPos, SIGNAL(valueChanged(double)),
3553- this, SLOT(updateAngle(double)));
3554+ m_pVinylControlEnabled = new ControlObjectThreadMain(ControlObject::getControl(
3555+ ConfigKey(group, "vinylcontrol_enabled")));
3556+ m_pSignalEnabled = new ControlObjectThreadMain(ControlObject::getControl(
3557+ ConfigKey(group, "vinylcontrol_signal_enabled")));
3558+ m_pRate = new ControlObjectThreadMain(ControlObject::getControl(
3559+ ConfigKey(group, "rate")));
3560
3561 //Match the vinyl control's set RPM so that the spinny widget rotates at the same
3562 //speed as your physical decks, if you're using vinyl control.
3563 connect(m_pVinylControlSpeedType, SIGNAL(valueChanged(double)),
3564 this, SLOT(updateVinylControlSpeed(double)));
3565+
3566+ //Make sure vinyl control proxies are up to date
3567+ connect(m_pVinylControlEnabled, SIGNAL(valueChanged(double)),
3568+ this, SLOT(updateVinylControlEnabled(double)));
3569+
3570+ //Check the rate to see if we are stopped
3571+ connect(m_pRate, SIGNAL(valueChanged(double)),
3572+ this, SLOT(updateRate(double)));
3573+#endif
3574 }
3575
3576 void WSpinny::paintEvent(QPaintEvent *e)
3577@@ -121,6 +156,49 @@
3578 if (m_pBG) {
3579 p.drawPixmap(0, 0, *m_pBG);
3580 }
3581+
3582+#ifdef __VINYLCONTROL__
3583+ // Overlay the signal quality drawing if vinyl is active
3584+ if (m_bVinylActive && m_bSignalActive)
3585+ {
3586+ //reduce cpu load by only updating every 3 times
3587+ m_iSignalUpdateTick = (m_iSignalUpdateTick + 1) % 3;
3588+ if (m_iSignalUpdateTick == 0)
3589+ {
3590+ unsigned char * buf = m_pVinylControl->getScopeBytemap();
3591+ int r,g,b;
3592+ QColor qual_color = QColor();
3593+ float signalQuality = m_pVinylControl->getTimecodeQuality();
3594+
3595+ //color is related to signal quality
3596+ //hsv: s=1, v=1
3597+ //h is the only variable.
3598+ //h=0 is red, h=120 is green
3599+ qual_color.setHsv((int)(120.0 * signalQuality), 255, 255);
3600+ qual_color.getRgb(&r, &g, &b);
3601+
3602+ if (buf) {
3603+ for (int y=0; y<m_iSize; y++) {
3604+ QRgb *line = (QRgb *)m_qImage.scanLine(y);
3605+ for(int x=0; x<m_iSize; x++) {
3606+ //use xwax's bitmap to set alpha data only
3607+ //adjust alpha by 3/4 so it's not quite so distracting
3608+ //setpixel is slow, use scanlines instead
3609+ //m_qImage.setPixel(x, y, qRgba(r,g,b,(int)buf[x+m_iSize*y] * .75));
3610+ *line = qRgba(r,g,b,(int)(buf[x+m_iSize*y] * .75));
3611+ line++;
3612+ }
3613+ }
3614+ p.drawImage(this->rect(), m_qImage);
3615+ }
3616+ }
3617+ else
3618+ {
3619+ //draw the last good image
3620+ p.drawImage(this->rect(), m_qImage);
3621+ }
3622+ }
3623+#endif
3624
3625 //To rotate the foreground pixmap around the center of the image,
3626 //we use the classic trick of translating the coordinate system such that
3627@@ -147,7 +225,7 @@
3628 //Rotate back to the playback position (not the ghost positon),
3629 //and draw the beat marks from there.
3630 p.restore();
3631-
3632+
3633 /*
3634 //Draw a line where the next 4 beats are
3635 double bpm = m_pBPM->get();
3636@@ -247,6 +325,29 @@
3637 void WSpinny::updateAngle(double playpos)
3638 {
3639 m_fAngle = calculateAngle(playpos);
3640+
3641+ // if we had the timer going, kill it
3642+ if (m_iTimerId != 0) {
3643+ killTimer(m_iTimerId);
3644+ m_iTimerId = 0;
3645+ }
3646+ update();
3647+}
3648+
3649+void WSpinny::updateRate(double rate)
3650+{
3651+ //if rate is zero, updateAngle won't get called,
3652+ if (rate == 0.0 && m_bVinylActive)
3653+ {
3654+ if (m_iTimerId == 0)
3655+ {
3656+ m_iTimerId = startTimer(10);
3657+ }
3658+ }
3659+}
3660+
3661+void WSpinny::timerEvent(QTimerEvent *event)
3662+{
3663 update();
3664 }
3665
3666@@ -266,6 +367,51 @@
3667 m_dRotationsPerSecond = rpm/60.;
3668 }
3669
3670+void WSpinny::updateVinylControlEnabled(double enabled)
3671+{
3672+#ifdef __VINYLCONTROL__
3673+ if (enabled)
3674+ {
3675+ if (m_pVinylControl == NULL)
3676+ {
3677+ m_pVinylControl = m_pVCManager->getVinylControlProxyForChannel(m_group);
3678+ if (m_pVinylControl != NULL)
3679+ {
3680+ m_bVinylActive = true;
3681+ m_bSignalActive = m_pSignalEnabled->get();
3682+ connect(m_pVinylControl, SIGNAL(destroyed()),
3683+ this, SLOT(invalidateVinylControl()));
3684+ }
3685+ }
3686+ else
3687+ {
3688+ m_bVinylActive = true;
3689+ }
3690+ }
3691+ else
3692+ {
3693+ m_bVinylActive = false;
3694+ //don't need the timer anymore
3695+ if (m_iTimerId != 0)
3696+ {
3697+ killTimer(m_iTimerId);
3698+ }
3699+ // draw once more to erase signal
3700+ update();
3701+ }
3702+#endif
3703+}
3704+
3705+void WSpinny::invalidateVinylControl()
3706+{
3707+#ifdef __VINYLCONTROL__
3708+ m_bVinylActive = false;
3709+ m_pVinylControl = NULL;
3710+ update();
3711+#endif
3712+}
3713+
3714+
3715 void WSpinny::mouseMoveEvent(QMouseEvent * e)
3716 {
3717 int y = e->y();
3718@@ -301,7 +447,7 @@
3719 //qDebug() << "c t:" << theta << "pt:" << m_dPrevTheta <<
3720 // "icr" << m_iFullRotations;
3721
3722- if (e->buttons() & Qt::LeftButton)
3723+ if (e->buttons() & Qt::LeftButton && !m_bVinylActive)
3724 {
3725 //Convert deltaTheta into a percentage of song length.
3726 double absPos = calculatePositionFromAngle(theta);
3727@@ -325,6 +471,10 @@
3728
3729 m_iStartMouseX = x;
3730 m_iStartMouseY = y;
3731+
3732+ //don't do anything if vinyl control is active
3733+ if (m_bVinylActive)
3734+ return;
3735
3736 if (e->button() == Qt::LeftButton)
3737 {
3738@@ -394,7 +544,7 @@
3739 Q_UNUSED(e); //ditch unused param warning
3740
3741 /*
3742- double wheelDirection = ((QWheelEvent *)e)->delta() / 120.;
3743+ double wheelDirection = ((QWheelEvent *)e)->delta() / 120.;
3744 double newValue = getValue() + (wheelDirection);
3745 this->updateValue(newValue);
3746
3747
3748=== modified file 'mixxx/src/widget/wspinny.h'
3749--- mixxx/src/widget/wspinny.h 2011-05-11 04:38:17 +0000
3750+++ mixxx/src/widget/wspinny.h 2011-10-04 14:01:27 +0000
3751@@ -4,6 +4,11 @@
3752
3753 #include <QGLWidget>
3754 #include "wwidget.h"
3755+#include "vinylcontrol/vinylcontrolmanager.h"
3756+#ifdef __VINYLCONTROL__
3757+#include "vinylcontrol/vinylcontrolproxy.h"
3758+#include "vinylcontrol/vinylcontrol.h"
3759+#endif
3760
3761 class ControlObjectThreadMain;
3762
3763@@ -11,15 +16,18 @@
3764 {
3765 Q_OBJECT
3766 public:
3767- WSpinny(QWidget* parent);
3768+ WSpinny(QWidget* parent, VinylControlManager* pVCMan);
3769 ~WSpinny();
3770 void setup(QDomNode node, QString group);
3771 void dragEnterEvent(QDragEnterEvent *event);
3772 void dropEvent(QDropEvent *event);
3773 public slots:
3774 void updateAngle(double);
3775+ void updateRate(double);
3776 void updateAngleForGhost();
3777 void updateVinylControlSpeed(double rpm);
3778+ void updateVinylControlEnabled(double enabled);
3779+ void invalidateVinylControl();
3780 signals:
3781 void trackDropped(QString filename, QString group);
3782 protected:
3783@@ -29,6 +37,7 @@
3784 void mousePressEvent(QMouseEvent * e);
3785 void mouseReleaseEvent(QMouseEvent * e);
3786 void wheelEvent(QWheelEvent *e);
3787+ void timerEvent(QTimerEvent* event);
3788
3789 double calculateAngle(double playpos);
3790 int calculateFullRotations(double playpos);
3791@@ -47,7 +56,22 @@
3792 ControlObjectThreadMain* m_pScratch;
3793 ControlObjectThreadMain* m_pScratchToggle;
3794 ControlObjectThreadMain* m_pScratchPos;
3795+ ControlObjectThreadMain* m_pRate;
3796 ControlObjectThreadMain* m_pVinylControlSpeedType;
3797+ ControlObjectThreadMain* m_pVinylControlEnabled;
3798+ ControlObjectThreadMain* m_pSignalEnabled;
3799+
3800+#ifdef __VINYLCONTROL__
3801+ VinylControlManager* m_pVCManager;
3802+ VinylControlProxy* m_pVinylControl;
3803+#endif
3804+ bool m_bVinylActive;
3805+ bool m_bSignalActive;
3806+ QImage m_qImage;
3807+ int m_iSize;
3808+ int m_iTimerId;
3809+ int m_iSignalUpdateTick;
3810+
3811 QString m_group;
3812 float m_fAngle; //Degrees
3813 float m_fGhostAngle;
3814
3815=== modified file 'mixxx/src/widget/wtracktableview.cpp'
3816--- mixxx/src/widget/wtracktableview.cpp 2011-09-25 02:17:38 +0000
3817+++ mixxx/src/widget/wtracktableview.cpp 2011-10-04 14:01:27 +0000
3818@@ -104,8 +104,12 @@
3819 * there's no need to exchange the headers
3820 * this will cause a small GUI freeze
3821 */
3822- if(getTrackModel() == track_model)
3823+ if (getTrackModel() == track_model) {
3824+ // Re-sort the table even if the track model is the same. This triggers
3825+ // a select() if the table is dirty.
3826+ doSortByColumn(horizontalHeader()->sortIndicatorSection());
3827 return;
3828+ }
3829
3830 setVisible(false);
3831
3832@@ -156,11 +160,8 @@
3833 //setSortingEnabled(true);
3834 connect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)),
3835 this, SLOT(doSortByColumn(int)), Qt::AutoConnection);
3836-
3837 doSortByColumn(horizontalHeader()->sortIndicatorSection());
3838
3839- sortByColumn(horizontalHeader()->sortIndicatorSection());
3840-
3841 // Initialize all column-specific things
3842 for (int i = 0; i < model->columnCount(); ++i) {
3843 // Setup delegates according to what the model tells us

Subscribers

People subscribed via source and target branches