Merge lp:~raskolnikov/mixxx/round-bpm into lp:~mixxxdevelopers/mixxx/trunk

Proposed by raskolnikov
Status: Rejected
Rejected by: Sean M. Pappalardo
Proposed branch: lp:~raskolnikov/mixxx/round-bpm
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 1795 lines (+1544/-9) (has conflicts)
16 files modified
mixxx/build/depends.py (+1/-0)
mixxx/res/controllers/M-Audio-Xponent-scripts.js (+2/-2)
mixxx/src/basetrackplayer.cpp (+24/-2)
mixxx/src/basetrackplayer.h (+4/-1)
mixxx/src/bpm/bpmround.cpp (+45/-0)
mixxx/src/bpm/bpmround.h (+31/-0)
mixxx/src/dlgprefbpm.cpp (+13/-3)
mixxx/src/dlgprefbpm.h (+1/-0)
mixxx/src/dlgprefbpmdlg.ui (+41/-0)
mixxx/src/dlgprefmidibindings.cpp.OTHER (+510/-0)
mixxx/src/mathstuff.h (+6/-0)
mixxx/src/track/beatgrid.cpp (+10/-0)
mixxx/src/track/beats.h (+7/-1)
mixxx/src/waveform/waveformrenderer.cpp.OTHER (+600/-0)
mixxx/src/waveform/waveformrenderer.h.OTHER (+96/-0)
mixxx/src/waveform/waveformrendersignal.cpp.OTHER (+153/-0)
Text conflict in mixxx/src/basetrackplayer.cpp
Text conflict in mixxx/src/dlgprefbpmdlg.ui
Contents conflict in mixxx/src/dlgprefmidibindings.cpp
Text conflict in mixxx/src/track/beatgrid.cpp
Text conflict in mixxx/src/track/beats.h
Contents conflict in mixxx/src/waveform/waveformrenderer.cpp
Contents conflict in mixxx/src/waveform/waveformrenderer.h
Contents conflict in mixxx/src/waveform/waveformrendersignal.cpp
To merge this branch: bzr merge lp:~raskolnikov/mixxx/round-bpm
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan Needs Fixing
raskolnikov (community) Needs Resubmitting
Phillip Whelan code review Needs Fixing
Review via email: mp+42349@code.launchpad.net

Description of the change

This branch actually includes two things:

a) A new option "round BPM on track load" that can be set to "no, round to integer, halves or quartes" that will round up the BPMs to the closest integer, half, or quarter. This is very useful when playing electronic music, which usually uses integer or half-integer BPM/sec. Because rounding the value can produce a loss of information in some cases, the valued stored in the db is not changed.

b) A bugfix on the midi bindings preferences dialog. Without this fix, the dialog executes the midiscript initialization always, now it only does so when you mess up with the bindings. The midiscript initialization can take some time in some devices.

To post a comment you must log in.
Revision history for this message
Sean M. Pappalardo (pegasus-renegadetech) wrote :

I committed your midi prefs dialog fix to trunk r2601, so you'll want to revert them in this branch then merge from trunk.

lp:~raskolnikov/mixxx/round-bpm updated
2596. By raskolnikov

Merged from trunk.

Revision history for this message
Phillip Whelan (pwhelan) wrote :

Two points have to be addressed immediately:

  * Use the floor and ceil functions provided by our code.
      The file src/engine/enginebufferscalelinear.cpp uses it
      in case you need an example.

  * The file src/dlgprefmidibindings.cpp has an unresolved conflict.
      This file has file markers indicative of an unresolved conflict.

review: Needs Fixing (code review)
lp:~raskolnikov/mixxx/round-bpm updated
2597. By Juan Pedro Bolívar Puente <email address hidden>

Merged trunk.

2598. By Juan Pedro Bolívar Puente <email address hidden>

Fixed bug with config not being stored.

Revision history for this message
raskolnikov (raskolnikov) wrote :

Hello,

I just adapted the fix to trunk. Also, the problem of the setting not being properly loaded have been fixed -- it seems that config obejct dislikes float values so now we just store an int.

On Philips comments, I can not find any floor or ceil function provided by mixxx. In mathstuff.h there is a round() function that can not be used for our purpose because it can only round up to integer.

On the other hand, in a discussion to rryan he was skeptical about the usefulness of this feature, because he argues that the BPM detector might be right sometimes by giving a non-round value. I myself mix most of the time with this turned on so I do find it useful at least. He suggested being able to turn this on/off per song. I think that that might complicate it a bit too much, and as compromise solution, I suggest adding a toggle in the main menu that is easy to access. In any case, I think that this is ready for merging into trunk -- the default behaviour is as it always was so there is no problem for users who do not like this feature -- and we can discuss those usability corners later.

review: Needs Resubmitting
lp:~raskolnikov/mixxx/round-bpm updated
2599. By Juan Pedro Bolívar Puente <email address hidden>

Compiler fix.

2600. By Juan Pedro Bolívar Puente <email address hidden>

Merged trunk.

2601. By Juan Pedro Bolívar Puente <email address hidden>

Merged trunk.

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Hi Juan,

It looks like the branch is missing bpmround.h and bpmround.cpp. Could you please add them?

Thanks,
RJ

review: Needs Fixing
lp:~raskolnikov/mixxx/round-bpm updated
2602. By Juan Pedro Bolívar Puente <email address hidden>

Added missing files.

2603. By Juan Pedro Bolívar Puente <email address hidden>

Added missing files.

2604. By Juan Pedro Bolívar Puente <email address hidden>

Merged trunk.

2605. By Juan Pedro Bolívar Puente <email address hidden>

Beatgrid now gets the rounded bpm value too.

2606. By Juan Pedro Bolívar Puente <email address hidden>

Low quality waveform for faster rendering.

Revision history for this message
raskolnikov (raskolnikov) wrote :

I think we can drop this branch given the accurate results of the new BPM editor.

Revision history for this message
Sean M. Pappalardo (pegasus-renegadetech) wrote :

Thanks for your work on this though, Raskolnikov! If you like, you may delete your personal branch whenever you're ready.

Unmerged revisions

2606. By Juan Pedro Bolívar Puente <email address hidden>

Low quality waveform for faster rendering.

2605. By Juan Pedro Bolívar Puente <email address hidden>

Beatgrid now gets the rounded bpm value too.

2604. By Juan Pedro Bolívar Puente <email address hidden>

Merged trunk.

2603. By Juan Pedro Bolívar Puente <email address hidden>

Added missing files.

2602. By Juan Pedro Bolívar Puente <email address hidden>

Added missing files.

2601. By Juan Pedro Bolívar Puente <email address hidden>

Merged trunk.

2600. By Juan Pedro Bolívar Puente <email address hidden>

Merged trunk.

2599. By Juan Pedro Bolívar Puente <email address hidden>

Compiler fix.

2598. By Juan Pedro Bolívar Puente <email address hidden>

Fixed bug with config not being stored.

2597. By Juan Pedro Bolívar Puente <email address hidden>

Merged trunk.

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 2012-05-14 20:47:43 +0000
3+++ mixxx/build/depends.py 2012-05-17 16:06:19 +0000
4@@ -505,6 +505,7 @@
5 "library/parsercsv.cpp",
6
7 "bpm/bpmscheme.cpp",
8+ "bpm/bpmround.cpp",
9
10 "soundsourceproxy.cpp",
11
12
13=== modified file 'mixxx/res/controllers/M-Audio-Xponent-scripts.js'
14--- mixxx/res/controllers/M-Audio-Xponent-scripts.js 2010-12-24 16:19:41 +0000
15+++ mixxx/res/controllers/M-Audio-Xponent-scripts.js 2012-05-17 16:06:19 +0000
16@@ -160,7 +160,7 @@
17 if (MaudioXponent.state["scratching"+currentdeck] == 1) { //scratch mode on
18 engine.scratchTick(currentdeck, value-64);
19 } else { //normal wheel mode
20- engine.setValue("[Channel"+currentdeck+"]", "jog", (value-64)/8);
21+ engine.setValue("[Channel"+currentdeck+"]", "jog", (value-64)/7.5);
22 }
23 }
24 };
25@@ -168,7 +168,7 @@
26 MaudioXponent.wheelbuton = function(channel, control, value, status) {
27 var currentdeck = channel+1;
28 if (MaudioXponent.state["scrmode"+currentdeck] == 1) { //scratch mode on
29- engine.scratchEnable(currentdeck, 3*128, 33+1/3, 1.0/8, (1.0/8)/32);
30+ engine.scratchEnable(currentdeck, 128*4., 44., 1.0/8, (1.0/8)/32);
31 MaudioXponent.state["scratching"+currentdeck] = true;
32 }
33 };
34
35=== modified file 'mixxx/src/basetrackplayer.cpp'
36--- mixxx/src/basetrackplayer.cpp 2012-05-16 20:42:04 +0000
37+++ mixxx/src/basetrackplayer.cpp 2012-05-17 16:06:19 +0000
38@@ -1,5 +1,6 @@
39 #include <QtCore>
40 #include <QMessageBox>
41+#include <cmath>
42
43 #include "basetrackplayer.h"
44 #include "playerinfo.h"
45@@ -8,6 +9,7 @@
46 #include "controlobject.h"
47 #include "controlpotmeter.h"
48 #include "trackinfoobject.h"
49+#include "track/beatfactory.h"
50 #include "engine/enginebuffer.h"
51 #include "engine/enginedeck.h"
52 #include "engine/enginemaster.h"
53@@ -15,8 +17,13 @@
54 #include "engine/cuecontrol.h"
55 #include "engine/clockcontrol.h"
56 #include "mathstuff.h"
57+<<<<<<< TREE
58 #include "track/beatgrid.h"
59 #include "waveform/renderers/waveformwidgetrenderer.h"
60+=======
61+#include "waveform/waveformrenderer.h"
62+#include "bpm/bpmround.h"
63+>>>>>>> MERGE-SOURCE
64
65 BaseTrackPlayer::BaseTrackPlayer(QObject* pParent,
66 ConfigObject<ConfigValue> *pConfig,
67@@ -113,6 +120,21 @@
68 delete m_pDuration;
69 }
70
71+float BaseTrackPlayer::roundBpm (float bpm)
72+{
73+ double roundedBpm = ::roundBpm (m_pConfig, bpm);
74+ if (roundedBpm != bpm && m_pLoadedTrack) {
75+ BeatsPointer beats = m_pLoadedTrack->getBeats();
76+ beats->setGrid(roundedBpm, beats->findNextBeat(0.0f));
77+ }
78+ return roundedBpm;
79+}
80+
81+void BaseTrackPlayer::slotSetBpm (double newBpm)
82+{
83+ m_pBPM->slotSet (roundBpm (newBpm));
84+}
85+
86 void BaseTrackPlayer::slotLoadTrack(TrackPointer track, bool bStartFromEndPos)
87 {
88 //Disconnect the old track's signals.
89@@ -155,7 +177,7 @@
90
91 // Listen for updates to the file's BPM
92 connect(m_pLoadedTrack.data(), SIGNAL(bpmUpdated(double)),
93- m_pBPM, SLOT(slotSet(double)));
94+ this, SLOT(slotSetBpm(double)));
95
96 // Listen for updates to the file's Replay Gain
97 connect(m_pLoadedTrack.data(), SIGNAL(ReplayGainUpdated(double)),
98@@ -210,7 +232,7 @@
99
100 // Update the BPM and duration values that are stored in ControlObjects
101 m_pDuration->set(m_pLoadedTrack->getDuration());
102- m_pBPM->slotSet(m_pLoadedTrack->getBpm());
103+ m_pBPM->slotSet(roundBpm(m_pLoadedTrack->getBpm()));
104 m_pReplayGain->slotSet(m_pLoadedTrack->getReplayGain());
105
106 // Update the PlayerInfo class that is used in EngineShoutcast to replace
107
108=== modified file 'mixxx/src/basetrackplayer.h'
109--- mixxx/src/basetrackplayer.h 2012-04-25 04:43:42 +0000
110+++ mixxx/src/basetrackplayer.h 2012-05-17 16:06:19 +0000
111@@ -31,13 +31,16 @@
112 void slotFinishLoading(TrackPointer pTrackInfoObject);
113 void slotLoadFailed(TrackPointer pTrackInfoObject, QString reason);
114 void slotUnloadTrack(TrackPointer track);
115-
116+ void slotSetBpm(double f);
117+
118 signals:
119 void loadTrack(TrackPointer pTrack);
120 void newTrackLoaded(TrackPointer pLoadedTrack);
121 void unloadingTrack(TrackPointer pAboutToBeUnloaded);
122
123 private:
124+ float roundBpm (float bpm);
125+
126 ConfigObject<ConfigValue>* m_pConfig;
127 TrackPointer m_pLoadedTrack;
128 AnalyserQueue* m_pAnalyserQueue;
129
130=== added file 'mixxx/src/bpm/bpmround.cpp'
131--- mixxx/src/bpm/bpmround.cpp 1970-01-01 00:00:00 +0000
132+++ mixxx/src/bpm/bpmround.cpp 2012-05-17 16:06:19 +0000
133@@ -0,0 +1,45 @@
134+/**
135+ * Time-stamp: <2011-04-09 13:50:44 raskolnikov>
136+ *
137+ * @file bpmround.cpp
138+ * @author Juan Pedro Bolívar Puente <raskolnikov@es.gnu.org>
139+ * @date Sat Apr 9 13:17:12 2011
140+ *
141+ * @brief Implementation of the utility functions.
142+ */
143+
144+#include <cmath>
145+#include <QDebug>
146+#include "bpmround.h"
147+
148+
149+const char* BPM_CONFIG_KEY = "[BPM]";
150+const float BPM_ROUNDINGFACTOR [] = { 0, 1, .5, .25, -1}; // End with negative!
151+
152+float roundBpm (ConfigObject<ConfigValue>* config, float value)
153+{
154+ int bpmFactor = config->getValueString (
155+ ConfigKey("[BPM]", "RoundingFactor")).toInt ();
156+ qDebug () << "Bpm rounding factor: " << config->getValueString (
157+ ConfigKey("[BPM]", "RoundingFactor"));
158+
159+ if (bpmFactor < 0 || bpmFactor >= BPM_NUM_ROUNDING)
160+ bpmFactor = BPM_ROUNDING_NONE;
161+
162+ return roundBpm ((BpmRounding) bpmFactor, value);
163+}
164+
165+float roundBpm (BpmRounding config, float value)
166+{
167+ float bpmFactor = BPM_ROUNDINGFACTOR [config];
168+
169+ if (bpmFactor > 0)
170+ {
171+ const float factored = value / bpmFactor;
172+ const float top = std::ceil (factored);
173+ const float bot = std::floor (factored);
174+ return (top - factored < factored - bot ? top : bot) * bpmFactor;
175+ }
176+
177+ return value;
178+}
179
180=== added file 'mixxx/src/bpm/bpmround.h'
181--- mixxx/src/bpm/bpmround.h 1970-01-01 00:00:00 +0000
182+++ mixxx/src/bpm/bpmround.h 2012-05-17 16:06:19 +0000
183@@ -0,0 +1,31 @@
184+/**
185+ * Time-stamp: <2011-04-09 13:49:55 raskolnikov>
186+ *
187+ * @file bpmround.h
188+ * @author Juan Pedro Bolívar Puente <raskolnikov@es.gnu.org>
189+ * @date Sat Apr 9 13:15:21 2011
190+ *
191+ * @brief Utility functions for rounding the bpm.
192+ */
193+
194+#ifndef MIXXX_BPMROUND_HPP_
195+#define MIXXX_BPMROUND_HPP_
196+
197+#include "configobject.h"
198+
199+enum BpmRounding
200+{
201+ BPM_ROUNDING_NONE,
202+ BPM_ROUNDING_INTEGER,
203+ BPM_ROUNDING_HALF,
204+ BPM_ROUNDING_QUARTER,
205+ BPM_NUM_ROUNDING,
206+};
207+
208+float roundBpm (ConfigObject<ConfigValue>* config, float value);
209+float roundBpm (BpmRounding config, float value);
210+
211+extern const char* BPM_CONFIG_KEY;
212+extern const float BPM_ROUNDING_FACTOR [];
213+
214+#endif /* MIXXX_BPMROUND_HPP_ */
215
216=== modified file 'mixxx/src/dlgprefbpm.cpp'
217--- mixxx/src/dlgprefbpm.cpp 2012-04-29 05:43:31 +0000
218+++ mixxx/src/dlgprefbpm.cpp 2012-05-17 16:06:19 +0000
219@@ -26,12 +26,13 @@
220 #include <QtCore>
221 #include <QMessageBox>
222
223+#include "mathstuff.h"
224 #include "dlgprefbpm.h"
225 #include "dlgbpmscheme.h"
226 #include "bpm/bpmscheme.h"
227 #include "xmlparse.h"
228
229-#define CONFIG_KEY "[BPM]"
230+static const char* CONFIG_KEY = "[BPM]";
231
232 DlgPrefBpm::DlgPrefBpm(QWidget * parent, ConfigObject<ConfigValue> * _config)
233 : QWidget(parent) {
234@@ -44,15 +45,14 @@
235 connect(chkWriteID3, SIGNAL(stateChanged(int)), this, SLOT(slotSetWriteID3Tag(int)));
236 connect(chkEnableBpmDetection, SIGNAL(stateChanged(int)), this, SLOT(slotSetBpmEnabled(int)));
237 connect(chkAboveRange, SIGNAL(stateChanged(int)), this, SLOT(slotSetAboveRange(int)));
238+ connect(cmbRounding, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetRounding(int)));
239
240 // TODO: Move this over the the scheme dialog
241-
242 connect(btnAdd, SIGNAL(pressed()), this, SLOT(slotAddBpmScheme()));
243 connect(btnEdit, SIGNAL(pressed()), this, SLOT(slotEditBpmScheme()));
244 connect(btnDelete, SIGNAL(pressed()), this, SLOT(slotDeleteBpmScheme()));
245 connect(btnDefault, SIGNAL(pressed()), this, SLOT(slotDefaultBpmScheme()));
246
247-
248 // Determine if the config value has already been set. If not, default to enabled
249 QString sBpmEnabled = config->getValueString(ConfigKey(CONFIG_KEY,"BPMDetectionEnabled"));
250 if(sBpmEnabled.isNull() || sBpmEnabled.isEmpty())
251@@ -90,6 +90,10 @@
252 chkWriteID3->setEnabled(false);
253 chkDetectOnImport->setEnabled(false);
254
255+ // Set default value for rounding checkbox
256+ int index = config->getValueString(ConfigKey(CONFIG_KEY,"RoundingFactor")).toFloat();
257+ cmbRounding->setCurrentIndex (index);
258+
259 // Load the BPM schemes
260 loadBpmSchemes();
261 populateBpmSchemeList();
262@@ -122,6 +126,12 @@
263 }
264 }
265
266+void DlgPrefBpm::slotSetRounding(int index)
267+{
268+ config->set(ConfigKey(CONFIG_KEY,"RoundingFactor"),
269+ ConfigValue(QString::number (index)));
270+}
271+
272 void DlgPrefBpm::slotSetBpmDetectOnImport(int)
273 {
274 if (chkDetectOnImport->isChecked())
275
276=== modified file 'mixxx/src/dlgprefbpm.h'
277--- mixxx/src/dlgprefbpm.h 2008-05-11 15:27:38 +0000
278+++ mixxx/src/dlgprefbpm.h 2012-05-17 16:06:19 +0000
279@@ -33,6 +33,7 @@
280 void slotSetBpmRangeStart(int);
281 void slotSetBpmRangeEnd(int);
282 void slotSetAboveRange(int);
283+ void slotSetRounding(int);
284
285 void slotEditBpmScheme();
286 void slotAddBpmScheme();
287
288=== modified file 'mixxx/src/dlgprefbpmdlg.ui'
289--- mixxx/src/dlgprefbpmdlg.ui 2012-04-30 18:38:29 +0000
290+++ mixxx/src/dlgprefbpmdlg.ui 2012-05-17 16:06:19 +0000
291@@ -38,9 +38,15 @@
292 </widget>
293 </item>
294 <item>
295+<<<<<<< TREE
296 <widget class="QCheckBox" name="chkDetectOnImport">
297 <property name="text">
298 <string>Detect Tracks BPM on Import</string>
299+=======
300+ <widget class="QCheckBox" name="chkDetectOnImport">
301+ <property name="text">
302+ <string>Detect Song BPM on Import</string>
303+>>>>>>> MERGE-SOURCE
304 </property>
305 </widget>
306 </item>
307@@ -61,6 +67,41 @@
308 </property>
309 </widget>
310 </item>
311+ <item>
312+ <layout class="QHBoxLayout" name="horizontalLayout">
313+ <item>
314+ <widget class="QLabel" name="label">
315+ <property name="text">
316+ <string>Round BPM on track load</string>
317+ </property>
318+ </widget>
319+ </item>
320+ <item>
321+ <widget class="QComboBox" name="cmbRounding">
322+ <item>
323+ <property name="text">
324+ <string>No</string>
325+ </property>
326+ </item>
327+ <item>
328+ <property name="text">
329+ <string>To integer</string>
330+ </property>
331+ </item>
332+ <item>
333+ <property name="text">
334+ <string>To halves</string>
335+ </property>
336+ </item>
337+ <item>
338+ <property name="text">
339+ <string>To quarter</string>
340+ </property>
341+ </item>
342+ </widget>
343+ </item>
344+ </layout>
345+ </item>
346 </layout>
347 </widget>
348 </item>
349
350=== added file 'mixxx/src/dlgprefmidibindings.cpp.OTHER'
351--- mixxx/src/dlgprefmidibindings.cpp.OTHER 1970-01-01 00:00:00 +0000
352+++ mixxx/src/dlgprefmidibindings.cpp.OTHER 2012-05-17 16:06:19 +0000
353@@ -0,0 +1,510 @@
354+/***************************************************************************
355+ dlgprefmidibindings.cpp - description
356+ -------------------
357+ begin : Sat Jun 21 2008
358+ copyright : (C) 2008 by Tom Care
359+ email : psyc0de@gmail.com
360+ ***************************************************************************/
361+
362+/***************************************************************************
363+ * *
364+ * This program is free software; you can redistribute it and/or modify *
365+ * it under the terms of the GNU General Public License as published by *
366+ * the Free Software Foundation; either version 2 of the License, or *
367+ * (at your option) any later version. *
368+ * *
369+ ***************************************************************************/
370+#include <QtGui>
371+#include <QDebug>
372+#include "midi/midiinputmappingtablemodel.h"
373+#include "midi/midioutputmappingtablemodel.h"
374+#include "midi/midichanneldelegate.h"
375+#include "midi/midistatusdelegate.h"
376+#include "midi/midinodelegate.h"
377+#include "midi/midioptiondelegate.h"
378+#include "controlgroupdelegate.h"
379+#include "controlvaluedelegate.h"
380+#include "dlgprefmidibindings.h"
381+#include "midi/mididevice.h"
382+#include "midi/mididevicemanager.h"
383+#include "configobject.h"
384+#include "midi/midimapping.h"
385+
386+#ifdef __MIDISCRIPT__
387+#include "midi/midiscriptengine.h"
388+#endif
389+
390+
391+DlgPrefMidiBindings::DlgPrefMidiBindings(QWidget *parent, MidiDevice* midiDevice,
392+ MidiDeviceManager* midiDeviceManager,
393+ ConfigObject<ConfigValue> *pConfig)
394+ : QWidget(parent), Ui::DlgPrefMidiBindingsDlg(), m_bDirty (false)
395+{
396+ setupUi(this);
397+ m_pConfig = pConfig;
398+ m_pMidiDevice = midiDevice;
399+ m_pMidiDeviceManager = midiDeviceManager;
400+
401+ m_pDlgMidiLearning = NULL;
402+
403+ m_bDirty = false;
404+
405+ labelDeviceName->setText(m_pMidiDevice->getName());
406+
407+ //Tell the input mapping table widget which data model it should be viewing
408+ //(note that m_pInputMappingTableView is defined in the .ui file!)
409+ m_pInputMappingTableView->setModel((QAbstractItemModel*)m_pMidiDevice->getMidiMapping()->getMidiInputMappingTableModel());
410+
411+ m_pInputMappingTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
412+ m_pInputMappingTableView->setSelectionMode(QAbstractItemView::ContiguousSelection); //The model won't like ExtendedSelection, probably.
413+ m_pInputMappingTableView->verticalHeader()->hide();
414+
415+ //Set up "delete" as a shortcut key to remove a row for the MIDI input table.
416+ m_deleteMIDIInputRowAction = new QAction(m_pInputMappingTableView);
417+ /*m_deleteMIDIInputRowAction->setShortcut(QKeySequence::Delete);
418+ m_deleteMIDIInputRowAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
419+ connect(m_deleteMIDIInputRowAction, SIGNAL(triggered()), this, SLOT(slotRemoveInputBinding()));
420+ */
421+ //The above shortcut doesn't work yet, not quite sure why. -- Albert Feb 1 / 2009
422+
423+ //Set up the cool item delegates for the input mapping table
424+ m_pMidiChannelDelegate = new MidiChannelDelegate(m_pInputMappingTableView);
425+ m_pMidiStatusDelegate = new MidiStatusDelegate(m_pInputMappingTableView);
426+ m_pMidiNoDelegate = new MidiNoDelegate(m_pInputMappingTableView);
427+ m_pMidiOptionDelegate = new MidiOptionDelegate(m_pInputMappingTableView);
428+ m_pControlGroupDelegate = new ControlGroupDelegate(m_pInputMappingTableView);
429+ m_pControlValueDelegate = new ControlValueDelegate(m_pInputMappingTableView);
430+ m_pInputMappingTableView->setItemDelegateForColumn(MIDIINPUTTABLEINDEX_MIDISTATUS, m_pMidiStatusDelegate);
431+ m_pInputMappingTableView->setItemDelegateForColumn(MIDIINPUTTABLEINDEX_MIDICHANNEL, m_pMidiChannelDelegate);
432+ m_pInputMappingTableView->setItemDelegateForColumn(MIDIINPUTTABLEINDEX_MIDINO, m_pMidiNoDelegate);
433+ m_pInputMappingTableView->setItemDelegateForColumn(MIDIINPUTTABLEINDEX_CONTROLOBJECTGROUP, m_pControlGroupDelegate);
434+ m_pInputMappingTableView->setItemDelegateForColumn(MIDIINPUTTABLEINDEX_CONTROLOBJECTVALUE, m_pControlValueDelegate);
435+ m_pInputMappingTableView->setItemDelegateForColumn(MIDIINPUTTABLEINDEX_MIDIOPTION, m_pMidiOptionDelegate);
436+
437+ //Tell the output mapping table widget which data model it should be viewing
438+ //(note that m_pOutputMappingTableView is defined in the .ui file!)
439+ m_pOutputMappingTableView->setModel((QAbstractItemModel*)m_pMidiDevice->getMidiMapping()->getMidiOutputMappingTableModel());
440+ m_pOutputMappingTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
441+ m_pOutputMappingTableView->setSelectionMode(QAbstractItemView::ContiguousSelection);
442+ m_pOutputMappingTableView->verticalHeader()->hide();
443+
444+ //Set up the cool item delegates for the output mapping table
445+ m_pOutputMappingTableView->setItemDelegateForColumn(MIDIOUTPUTTABLEINDEX_MIDISTATUS, m_pMidiStatusDelegate);
446+ m_pOutputMappingTableView->setItemDelegateForColumn(MIDIOUTPUTTABLEINDEX_MIDICHANNEL, m_pMidiChannelDelegate);
447+ m_pOutputMappingTableView->setItemDelegateForColumn(MIDIOUTPUTTABLEINDEX_MIDINO, m_pMidiNoDelegate);
448+ //TODO: We need different delegates for the output table's CO group/value columns because we only list real input
449+ // controls, and for output we'd want to list a different set with stuff like "VUMeter" and other output controls.
450+ //m_pOutputMappingTableView->setItemDelegateForColumn(MIDIOUTPUTTABLEINDEX_CONTROLOBJECTGROUP, m_pControlGroupDelegate);
451+ //m_pOutputMappingTableView->setItemDelegateForColumn(MIDIOUTPUTTABLEINDEX_CONTROLOBJECTVALUE, m_pControlValueDelegate);
452+
453+ // Connect buttons to slots
454+ connect(btnExportXML, SIGNAL(clicked()), this, SLOT(slotExportXML()));
455+
456+ //Input bindings
457+ connect(btnMidiLearnWizard, SIGNAL(clicked()), this, SLOT(slotShowMidiLearnDialog()));
458+ connect(btnMidiLearnWizard, SIGNAL(clicked()), this, SLOT(slotDirty()));
459+ connect(btnClearAllInputBindings, SIGNAL(clicked()), this, SLOT(slotClearAllInputBindings()));
460+ connect(btnClearAllInputBindings, SIGNAL(clicked()), this, SLOT(slotDirty()));
461+ connect(btnRemoveInputBinding, SIGNAL(clicked()), this, SLOT(slotRemoveInputBinding()));
462+ connect(btnRemoveInputBinding, SIGNAL(clicked()), this, SLOT(slotDirty()));
463+ connect(btnAddInputBinding, SIGNAL(clicked()), this, SLOT(slotAddInputBinding()));
464+ connect(btnAddInputBinding, SIGNAL(clicked()), this, SLOT(slotDirty()));
465+
466+ //Output bindings
467+ connect(btnClearAllOutputBindings, SIGNAL(clicked()), this, SLOT(slotClearAllOutputBindings()));
468+ connect(btnClearAllOutputBindings, SIGNAL(clicked()), this, SLOT(slotDirty()));
469+ connect(btnRemoveOutputBinding, SIGNAL(clicked()), this, SLOT(slotRemoveOutputBinding()));
470+ connect(btnRemoveOutputBinding, SIGNAL(clicked()), this, SLOT(slotDirty()));
471+ connect(btnAddOutputBinding, SIGNAL(clicked()), this, SLOT(slotAddOutputBinding()));
472+ connect(btnAddOutputBinding, SIGNAL(clicked()), this, SLOT(slotDirty()));
473+
474+ connect(comboBoxPreset, SIGNAL(activated(const QString&)), this, SLOT(slotLoadMidiMapping(const QString&)));
475+ connect(comboBoxPreset, SIGNAL(activated(const QString&)), this, SLOT(slotDirty()));
476+
477+ connect(m_pInputMappingTableView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(slotDirty()));
478+ connect(m_pOutputMappingTableView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(slotDirty()));
479+
480+ //Load the list of presets into the presets combobox.
481+ enumeratePresets();
482+
483+ //Initialize the output device combobox
484+ enumerateOutputDevices();
485+
486+}
487+
488+DlgPrefMidiBindings::~DlgPrefMidiBindings() {
489+ delete m_deleteMIDIInputRowAction;
490+}
491+
492+void DlgPrefMidiBindings::slotDirty ()
493+{
494+ m_bDirty = true;
495+}
496+
497+void DlgPrefMidiBindings::enumerateOutputDevices()
498+{
499+ comboBoxOutputDevice->clear();
500+
501+ comboBoxOutputDevice->addItem(tr("None"));
502+
503+ //For each MIDI output device, insert an item into the output device combobox.
504+ QList<MidiDevice*> deviceList = m_pMidiDeviceManager->getDeviceList(true, false);
505+ QListIterator<MidiDevice*> it(deviceList);
506+
507+ while (it.hasNext())
508+ {
509+ MidiDevice* currentDevice = it.next();
510+ QString curDeviceName = currentDevice->getName();
511+ //qDebug() << "curDeviceName: " << curDeviceName;
512+ comboBoxOutputDevice->addItem(curDeviceName);
513+ }
514+
515+ //Assume autopairing was done and let's just show the output device combobox with the name
516+ //of the input device selected for now...
517+ QString currentOutputMidiDeviceName = m_pMidiDevice->getName();
518+ comboBoxOutputDevice->setCurrentIndex(comboBoxOutputDevice->findText(currentOutputMidiDeviceName));
519+
520+}
521+
522+void DlgPrefMidiBindings::enumeratePresets()
523+{
524+ QList<QString> presetsList;
525+ comboBoxPreset->clear();
526+
527+ //Insert a dummy "..." item at the top to try to make it less confusing.
528+ //(For example, we don't want "Akai MPD24" showing up as the default item
529+ // when a user has their controller plugged in)
530+ comboBoxPreset->addItem("...");
531+
532+ // paths to search for midi presets
533+ QList<QString> midiDirPaths;
534+ midiDirPaths.append(LPRESETS_PATH);
535+ midiDirPaths.append(m_pConfig->getConfigPath().append("midi/"));
536+
537+ QListIterator<QString> itpth(midiDirPaths);
538+ while (itpth.hasNext()) {
539+ QDirIterator it(itpth.next());
540+ while (it.hasNext())
541+ {
542+ it.next(); //Advance iterator. We get the filename from the next line. (It's a bit weird.)
543+ QString curMapping = it.fileName();
544+ if (curMapping.endsWith(MIDI_MAPPING_EXTENSION)) //blah, thanks for nothing Qt
545+ {
546+ curMapping.chop(QString(MIDI_MAPPING_EXTENSION).length()); //chop off the .midi.xml
547+ presetsList.append(curMapping);
548+ }
549+ }
550+ }
551+ //Sort in alphabetical order
552+ qSort(presetsList);
553+ comboBoxPreset->addItems(presetsList);
554+}
555+
556+
557+
558+/* slotUpdate()
559+ * Called when the dialog is displayed.
560+ */
561+void DlgPrefMidiBindings::slotUpdate() {
562+
563+ //Check if the device that this dialog is for is already enabled...
564+ if (m_pMidiDevice->isOpen())
565+ {
566+ chkEnabledDevice->setCheckState(Qt::Checked); //Check the "Enabled" box
567+ toolBox->setEnabled(true); //Enable MIDI in/out toolbox.
568+ groupBoxPresets->setEnabled(true); //Enable presets group box.
569+ }
570+ else {
571+ chkEnabledDevice->setCheckState(Qt::Unchecked); //Uncheck the "Enabled" box
572+ toolBox->setEnabled(false); //Disable MIDI in/out toolbox.
573+ groupBoxPresets->setEnabled(false); //Disable presets group box.
574+ }
575+
576+ //Connect the "Enabled" checkbox after the checkbox state is set
577+ connect(chkEnabledDevice, SIGNAL(stateChanged(int)), this, SLOT(slotDeviceState(int)));
578+ connect(chkEnabledDevice, SIGNAL(stateChanged(int)), this, SLOT(slotDirty()));
579+}
580+
581+/* slotApply()
582+ * Called when the OK button is pressed.
583+ */
584+void DlgPrefMidiBindings::slotApply() {
585+ /* User has pressed OK, so enable or disable the device, write the
586+ * controls to the DOM, and reload the MIDI bindings. FIXED: only
587+ * do this if the user has changed the preferences.
588+ */
589+ if (m_bDirty)
590+ {
591+ m_pMidiDevice->disableMidiLearn();
592+ if (chkEnabledDevice->isChecked()) {
593+ //Enable the device.
594+ enableDevice();
595+
596+ //Disable processing of MIDI messages received from the device in order to
597+ //prevent a race condition while we modify the MIDI mapping.
598+ m_pMidiDevice->setReceiveInhibit(true);
599+ m_pMidiDevice->getMidiMapping()->applyPreset();
600+ m_pMidiDevice->setReceiveInhibit(false);
601+
602+ //FIXME: We need some logic like this to make changing the output device work.
603+ // See MidiDeviceManager::associateInputAndOutputDevices() for more info...
604+ /*
605+ if (comboBoxOutputDevice->currentText() != tr("None"))
606+ m_pMidiDeviceManager->associateInputAndOutputDevices(m_pMidiDevice, comboBoxOutputDevice->currentText());
607+ */
608+ }
609+ else disableDevice();
610+ }
611+ m_bDirty = false;
612+}
613+
614+void DlgPrefMidiBindings::slotShowMidiLearnDialog() {
615+
616+ //If the user has checked the "Enabled" checkbox but they haven't
617+ //hit OK to apply it yet, prompt them to apply the settings before we open
618+ //the MIDI learning dialog. If we don't apply the settings first and open the device,
619+ //MIDI learn won't react to MIDI messages.
620+ if (chkEnabledDevice->isChecked() && !m_pMidiDevice->isOpen())
621+ {
622+ QMessageBox::StandardButton result = QMessageBox::question(this,
623+ tr("Apply MIDI device settings?"),
624+ tr("Your settings must be applied before starting the MIDI learning wizard.\n"
625+ "Apply settings and continue?"));
626+ if (result == QMessageBox::Cancel)
627+ {
628+ return;
629+ }
630+ else
631+ {
632+ slotApply();
633+ }
634+ }
635+
636+ //Note that DlgMidiLearning is set to delete itself on
637+ //close using the Qt::WA_DeleteOnClose attribute (so this "new" doesn't leak memory)
638+ m_pDlgMidiLearning = new DlgMidiLearning(this, m_pMidiDevice->getMidiMapping());
639+ m_pDlgMidiLearning->show();
640+}
641+
642+/* slotImportXML()
643+ * Prompts the user for an XML preset and loads it.
644+ */
645+void DlgPrefMidiBindings::slotLoadMidiMapping(const QString &name) {
646+
647+ if (name == "...")
648+ return;
649+
650+ //Ask for confirmation if the MIDI tables aren't empty...
651+ MidiMapping* mapping = m_pMidiDevice->getMidiMapping();
652+ if (mapping->numInputMidiMessages() > 0 ||
653+ mapping->numOutputMixxxControls() > 0)
654+ {
655+ QMessageBox::StandardButton result = QMessageBox::question(
656+ this,
657+ tr("Overwrite existing mapping?"),
658+ tr("Are you sure you'd like to load the %1 mapping?\n"
659+ "This will overwrite your existing MIDI mapping.").arg(name),
660+ QMessageBox::Yes | QMessageBox::No);
661+
662+ if (result == QMessageBox::No) {
663+ //Select the "..." item again in the combobox.
664+ comboBoxPreset->setCurrentIndex(0);
665+ return;
666+ }
667+ }
668+
669+ QString filename = LPRESETS_PATH + name + MIDI_MAPPING_EXTENSION;
670+ QFile ftest(filename);
671+ if ( !ftest.exists() ) filename = m_pConfig->getConfigPath().append("midi/") + name + MIDI_MAPPING_EXTENSION;
672+
673+ if (!filename.isNull()) m_pMidiDevice->getMidiMapping()->loadPreset(filename, true); // It's applied on prefs close
674+ m_pInputMappingTableView->update();
675+
676+ //Select the "..." item again in the combobox.
677+ comboBoxPreset->setCurrentIndex(0);
678+}
679+
680+/* slotExportXML()
681+ * Prompts the user for an XML preset and saves it.
682+ */
683+void DlgPrefMidiBindings::slotExportXML() {
684+ QString fileName = QFileDialog::getSaveFileName(this,
685+ tr("Export Mixxx MIDI Bindings"), m_pConfig->getConfigPath().append("midi/"),
686+ tr("Preset Files (*.midi.xml)"));
687+ if (!fileName.isNull()) m_pMidiDevice->getMidiMapping()->savePreset(fileName);
688+}
689+
690+void DlgPrefMidiBindings::slotDeviceState(int state) {
691+ if (state == Qt::Checked) {
692+ toolBox->setEnabled(true); //Enable MIDI in/out toolbox.
693+ groupBoxPresets->setEnabled(true); //Enable presets group box.
694+ emit deviceStateChanged(this,true); // Set tree item text to bold
695+ }
696+ else {
697+ toolBox->setEnabled(false); //Disable MIDI in/out toolbox.
698+ groupBoxPresets->setEnabled(false); //Disable presets group box.
699+ emit deviceStateChanged(this,false); // Set tree item text to not bold
700+ }
701+}
702+
703+void DlgPrefMidiBindings::enableDevice()
704+{
705+ if (m_pMidiDevice->isOpen()) {
706+ m_pMidiDevice->close();
707+ }
708+ m_pMidiDevice->open();
709+ m_pConfig->set(ConfigKey("[Midi]", m_pMidiDevice->getName().replace(" ", "_")), 1);
710+
711+ //TODO: Should probably check if open() actually succeeded.
712+}
713+
714+void DlgPrefMidiBindings::disableDevice()
715+{
716+ m_pMidiDevice->close();
717+ m_pConfig->set(ConfigKey("[Midi]", m_pMidiDevice->getName().replace(" ", "_")), 0);
718+
719+ //TODO: Should probably check if close() actually succeeded.
720+}
721+
722+void DlgPrefMidiBindings::slotAddInputBinding()
723+{
724+ bool ok = true;
725+ QString controlGroup = QInputDialog::getItem(this, tr("Select Control Group"), tr("Select Control Group"),
726+ ControlGroupDelegate::getControlGroups(), 0, false, &ok);
727+ if (!ok) return;
728+
729+ QStringList controlValues;
730+ if (controlGroup == CONTROLGROUP_CHANNEL1_STRING ||
731+ controlGroup == CONTROLGROUP_CHANNEL2_STRING ||
732+ controlGroup == CONTROLGROUP_SAMPLER1_STRING ||
733+ controlGroup == CONTROLGROUP_SAMPLER2_STRING ||
734+ controlGroup == CONTROLGROUP_SAMPLER3_STRING ||
735+ controlGroup == CONTROLGROUP_SAMPLER4_STRING) {
736+ controlValues = ControlValueDelegate::getChannelControlValues();
737+ }
738+ else if (controlGroup == CONTROLGROUP_MASTER_STRING)
739+ {
740+ controlValues = ControlValueDelegate::getMasterControlValues();
741+ }
742+ else if (controlGroup == CONTROLGROUP_PLAYLIST_STRING)
743+ {
744+ controlValues = ControlValueDelegate::getPlaylistControlValues();
745+ }
746+ else if (controlGroup == CONTROLGROUP_FLANGER_STRING)
747+ {
748+ controlValues = ControlValueDelegate::getFlangerControlValues();
749+ }
750+ else if (controlGroup == CONTROLGROUP_MICROPHONE_STRING)
751+ {
752+ controlValues = ControlValueDelegate::getMicrophoneControlValues();
753+ }
754+ else
755+ {
756+ qDebug() << "Unhandled ControlGroup in " << __FILE__;
757+ }
758+
759+
760+ QString controlValue = QInputDialog::getItem(this, tr("Select Control"), tr("Select Control"),
761+ controlValues, 0, false, &ok);
762+ if (!ok) return;
763+
764+
765+ MixxxControl mixxxControl(controlGroup, controlValue);
766+ MidiMessage message;
767+
768+ while (m_pMidiDevice->getMidiMapping()->isMidiMessageMapped(message))
769+ {
770+ message.setMidiNo(message.getMidiNo() + 1);
771+ if (message.getMidiNo() >= 127) //If the table is full, then overwrite something...
772+ break;
773+ }
774+ m_pMidiDevice->getMidiMapping()->setInputMidiMapping(message, mixxxControl);
775+}
776+
777+void DlgPrefMidiBindings::slotRemoveInputBinding()
778+{
779+ QModelIndexList selectedIndices = m_pInputMappingTableView->selectionModel()->selectedRows();
780+ if (selectedIndices.size() > 0)
781+ {
782+ MidiInputMappingTableModel* tableModel = dynamic_cast<MidiInputMappingTableModel*>(m_pInputMappingTableView->model());
783+ if (tableModel) {
784+
785+ QModelIndex curIndex;
786+ //The model indices are sorted so that we remove the rows from the table
787+ //in ascending order. This is necessary because if row A is above row B in
788+ //the table, and you remove row A, the model index for row B will change.
789+ //Sorting the indices first means we don't have to worry about this.
790+ qSort(selectedIndices);
791+
792+ //Going through the model indices in descending order (see above comment for explanation).
793+ QListIterator<QModelIndex> it(selectedIndices);
794+ it.toBack();
795+ while (it.hasPrevious())
796+ {
797+ curIndex = it.previous();
798+ tableModel->removeRow(curIndex.row());
799+ }
800+ }
801+ }
802+}
803+
804+void DlgPrefMidiBindings::slotClearAllInputBindings() {
805+ if (QMessageBox::warning(this, tr("Clear Input Bindings"),
806+ tr("Are you sure you want to clear all bindings?"),
807+ QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Ok)
808+ return;
809+
810+ //Remove all the rows from the data model (ie. the MIDI mapping).
811+ MidiInputMappingTableModel* tableModel = dynamic_cast<MidiInputMappingTableModel*>(m_pInputMappingTableView->model());
812+ if (tableModel) {
813+ tableModel->removeRows(0, tableModel->rowCount());
814+ }
815+}
816+
817+
818+void DlgPrefMidiBindings::slotAddOutputBinding() {
819+ qDebug() << "STUB: DlgPrefMidiBindings::slotAddOutputBinding()";
820+
821+ m_pMidiDevice->getMidiMapping()->setOutputMidiMapping(MixxxControl(), MidiMessage());
822+}
823+
824+void DlgPrefMidiBindings::slotRemoveOutputBinding()
825+{
826+ QModelIndexList selectedIndices = m_pOutputMappingTableView->selectionModel()->selectedRows();
827+ if (selectedIndices.size() > 0)
828+ {
829+ MidiOutputMappingTableModel* tableModel =
830+ dynamic_cast<MidiOutputMappingTableModel*>(m_pOutputMappingTableView->model());
831+ if (tableModel) {
832+ QModelIndex curIndex;
833+ //The model indices are sorted so that we remove the rows from the table
834+ //in ascending order. This is necessary because if row A is above row B in
835+ //the table, and you remove row A, the model index for row B will change.
836+ //Sorting the indices first means we don't have to worry about this.
837+ //qSort(selectedIndices);
838+
839+ //Going through the model indices in descending order (see above comment for explanation).
840+ QListIterator<QModelIndex> it(selectedIndices);
841+ it.toBack();
842+ while (it.hasPrevious())
843+ {
844+ curIndex = it.previous();
845+ qDebug() << "Dlg: removing row" << curIndex.row();
846+ tableModel->removeRow(curIndex.row());
847+ }
848+ }
849+ }
850+}
851+
852+void DlgPrefMidiBindings::slotClearAllOutputBindings() {
853+ if (QMessageBox::warning(this, tr("Clear Output Bindings"),
854+ tr("Are you sure you want to clear all output bindings?"),
855+ QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Ok)
856+ return;
857+
858+ //Remove all the rows from the data model (ie. the MIDI mapping).
859+ MidiOutputMappingTableModel* tableModel = dynamic_cast<MidiOutputMappingTableModel*>(m_pOutputMappingTableView->model());
860+ if (tableModel) {
861+ tableModel->removeRows(0, tableModel->rowCount());
862+ }
863+}
864
865=== modified file 'mixxx/src/mathstuff.h'
866--- mixxx/src/mathstuff.h 2012-03-20 23:04:44 +0000
867+++ mixxx/src/mathstuff.h 2012-05-17 16:06:19 +0000
868@@ -42,6 +42,12 @@
869 double qip(CSAMPLE x, unsigned int n);
870 float sigmoid_zero(double t, double max_t);
871
872+template<typename T>
873+T rEqual(T val, T other, T error)
874+{
875+ return val - error > other && other < val + error;
876+}
877+
878 static CSAMPLE pi = acos(-1.0f);
879 static CSAMPLE two_pi = (2.f*acos(-1.f));
880
881
882=== modified file 'mixxx/src/track/beatgrid.cpp'
883--- mixxx/src/track/beatgrid.cpp 2012-05-07 05:30:14 +0000
884+++ mixxx/src/track/beatgrid.cpp 2012-05-17 16:06:19 +0000
885@@ -40,6 +40,16 @@
886 m_mutex(QMutex::Recursive),
887 m_iSampleRate(pTrack->getSampleRate()),
888 m_dBeatLength(0.0f) {
889+<<<<<<< TREE
890+=======
891+ // [raskolnikov] called indirectly
892+ //
893+ // connect(pTrack.data(), SIGNAL(bpmUpdated(double)),
894+ // this, SLOT(slotTrackBpmUpdated(double)),
895+ // Qt::DirectConnection);
896+ slotTrackBpmUpdated(pTrack->getBpm());
897+
898+>>>>>>> MERGE-SOURCE
899 qDebug() << "New BeatGrid";
900 if (pByteArray != NULL) {
901 readByteArray(pByteArray);
902
903=== modified file 'mixxx/src/track/beats.h'
904--- mixxx/src/track/beats.h 2012-05-07 05:30:14 +0000
905+++ mixxx/src/track/beats.h 2012-05-17 16:06:19 +0000
906@@ -49,7 +49,6 @@
907 ////////////////////////////////////////////////////////////////////////////
908 // Beat calculations
909 ////////////////////////////////////////////////////////////////////////////
910-
911 // Starting from sample dSamples, return the sample of the next beat in the
912 // track, or -1 if none exists. If dSamples refers to the location of a
913 // beat, dSamples is returned.
914@@ -108,10 +107,17 @@
915 // Scale the position of every beat in the song by dScalePercentage. Beats
916 // class must have the capability BEATSCAP_SCALE.
917 virtual void scale(double dScalePercentage) = 0;
918+<<<<<<< TREE
919
920 // Adjust the beats so the global average BPM matches dBpm. Beats class must
921 // have the capability BEATSCAP_SET.
922 virtual void setBpm(double dBpm) = 0;
923+=======
924+
925+ // [raskolnikov] HACK
926+ virtual void setGrid(double bpm, double first) {};
927+
928+>>>>>>> MERGE-SOURCE
929 };
930
931 #endif /* BEATS_H */
932
933=== added file 'mixxx/src/waveform/waveformrenderer.cpp.OTHER'
934--- mixxx/src/waveform/waveformrenderer.cpp.OTHER 1970-01-01 00:00:00 +0000
935+++ mixxx/src/waveform/waveformrenderer.cpp.OTHER 2012-05-17 16:06:19 +0000
936@@ -0,0 +1,600 @@
937+#include <QDebug>
938+#include <QDomNode>
939+#include <QImage>
940+#include <QListIterator>
941+#include <QObject>
942+
943+#include <time.h>
944+
945+#include "mathstuff.h"
946+#include "waveformrenderer.h"
947+#include "waveformrenderbackground.h"
948+#include "waveformrenderbeat.h"
949+#include "waveformrendermark.h"
950+#include "waveformrendermarkrange.h"
951+#include "waveformrendersignal.h"
952+#include "waveformrendersignalpixmap.h"
953+#include "trackinfoobject.h"
954+#include "soundsourceproxy.h"
955+#include "controlobjectthreadmain.h"
956+#include "controlobject.h"
957+#include "widget/wwidget.h"
958+#include "widget/wskincolor.h"
959+
960+#define INTERPOLATION 0
961+
962+#define DEFAULT_SUBPIXELS_PER_PIXEL 1.5
963+#define DEFAULT_PIXELS_PER_SECOND 100
964+
965+#define RATE_INCREMENT 0.015
966+
967+void WaveformRenderer::run() {
968+ double msecs_old = 0, msecs_elapsed = 0;
969+
970+ while(!m_bQuit) {
971+
972+ if(m_iLatency != 0 && m_dPlayPos != -1 && m_dPlayPosOld != -1 && m_iNumSamples != 0) {
973+ QTime now = QTime::currentTime();
974+ double msecs_elapsed = m_playPosTime.msecsTo(now);
975+ double timeratio = double(msecs_elapsed) / m_iLatency;
976+ double adjust = (m_dPlayPos - m_dPlayPosOld) * math_min(1.0f, timeratio);
977+ m_dPlayPosAdjust = adjust;
978+ }
979+
980+ QThread::msleep(6);
981+ }
982+}
983+
984+WaveformRenderer::WaveformRenderer(const char* group) :
985+ QThread(),
986+ m_pGroup(group),
987+ m_iWidth(0),
988+ m_iHeight(0),
989+ bgColor(0,0,0),
990+ signalColor(255,255,255),
991+ colorMarker(255,255,255),
992+ colorBeat(255,255,255),
993+ colorCue(255,255,255),
994+ m_iNumSamples(0),
995+ m_iPlayPosTime(-1),
996+ m_iPlayPosTimeOld(-1),
997+ m_dPlayPos(0),
998+ m_dPlayPosOld(-1),
999+ m_dRate(0),
1000+ m_dRateRange(0),
1001+ m_dRateDir(0),
1002+ m_iRateAdjusting(0),
1003+ m_iDupes(0),
1004+ m_dPlayPosAdjust(0),
1005+ m_iLatency(0),
1006+ m_pSampleBuffer(NULL),
1007+ m_pPixmap(NULL),
1008+ m_pImage(),
1009+ m_iSubpixelsPerPixel(DEFAULT_SUBPIXELS_PER_PIXEL),
1010+ m_iPixelsPerSecond(DEFAULT_PIXELS_PER_SECOND),
1011+ m_pTrack(NULL),
1012+ m_bQuit(false)
1013+{
1014+ m_pPlayPos = new ControlObjectThreadMain(
1015+ ControlObject::getControl(ConfigKey(group,"visual_playposition")));
1016+ if(m_pPlayPos != NULL)
1017+ connect(m_pPlayPos, SIGNAL(valueChanged(double)),
1018+ this, SLOT(slotUpdatePlayPos(double)));
1019+
1020+
1021+ m_pLatency = new ControlObjectThreadMain(
1022+ ControlObject::getControl(ConfigKey("[Master]","latency")));
1023+ if(m_pLatency != NULL)
1024+ connect(m_pLatency, SIGNAL(valueChanged(double)),
1025+ this, SLOT(slotUpdateLatency(double)));
1026+
1027+ m_pRenderBackground = new WaveformRenderBackground(group, this);
1028+ m_pRenderSignal = new WaveformRenderSignal(group, this);
1029+ m_pRenderSignalPixmap = new WaveformRenderSignalPixmap(group, this);
1030+ m_pRenderBeat = new WaveformRenderBeat(group, this);
1031+
1032+ m_pCOVisualResample = new ControlObject(ConfigKey(group, "VisualResample"));
1033+
1034+ m_pRate = new ControlObjectThreadMain(
1035+ ControlObject::getControl(ConfigKey(group, "rate")));
1036+ if(m_pRate != NULL) {
1037+ connect(m_pRate, SIGNAL(valueChanged(double)),
1038+ this, SLOT(slotUpdateRate(double)));
1039+ }
1040+
1041+ m_pRateRange = new ControlObjectThreadMain(
1042+ ControlObject::getControl(ConfigKey(group, "rateRange")));
1043+ if(m_pRateRange != NULL) {
1044+ connect(m_pRateRange, SIGNAL(valueChanged(double)),
1045+ this, SLOT(slotUpdateRateRange(double)));
1046+ }
1047+
1048+ m_pRateDir = new ControlObjectThreadMain(
1049+ ControlObject::getControl(ConfigKey(group, "rate_dir")));
1050+ if (m_pRateDir) {
1051+ connect(m_pRateDir, SIGNAL(valueChanged(double)),
1052+ this, SLOT(slotUpdateRateDir(double)));
1053+ }
1054+
1055+ if(0)
1056+ start();
1057+}
1058+
1059+
1060+WaveformRenderer::~WaveformRenderer() {
1061+ qDebug() << this << "~WaveformRenderer()";
1062+
1063+ // Wait for the thread to quit
1064+ m_bQuit = true;
1065+ QThread::wait();
1066+
1067+ if(m_pRenderBackground)
1068+ delete m_pRenderBackground;
1069+ m_pRenderBackground = NULL;
1070+
1071+ if(m_pRenderSignalPixmap)
1072+ delete m_pRenderSignalPixmap;
1073+ m_pRenderSignalPixmap = NULL;
1074+
1075+ if(m_pRenderSignal)
1076+ delete m_pRenderSignal;
1077+ m_pRenderSignal = NULL;
1078+
1079+ if(m_pRenderBeat)
1080+ delete m_pRenderBeat;
1081+ m_pRenderBeat = NULL;
1082+
1083+ QMutableListIterator<RenderObject*> iter(m_renderObjects);
1084+ while (iter.hasNext()) {
1085+ RenderObject* ro = iter.next();
1086+ iter.remove();
1087+ delete ro;
1088+ }
1089+
1090+ if(m_pCOVisualResample)
1091+ delete m_pCOVisualResample;
1092+ m_pCOVisualResample = NULL;
1093+
1094+ if (m_pPlayPos)
1095+ delete m_pPlayPos;
1096+ m_pPlayPos = NULL;
1097+
1098+ if (m_pLatency)
1099+ delete m_pLatency;
1100+ m_pLatency = NULL;;
1101+
1102+ if(m_pRate)
1103+ delete m_pRate;
1104+ m_pRate = NULL;
1105+
1106+ if(m_pRateRange)
1107+ delete m_pRateRange;
1108+ m_pRateRange = NULL;
1109+
1110+ if(m_pRateDir)
1111+ delete m_pRateDir;
1112+ m_pRateDir = NULL;
1113+
1114+ if(m_pPlayPos)
1115+ delete m_pPlayPos;
1116+ m_pPlayPos = NULL;
1117+}
1118+
1119+void WaveformRenderer::slotUpdatePlayPos(double v) {
1120+ m_iPlayPosTimeOld = m_iPlayPosTime;
1121+ //m_playPosTimeOld = m_playPosTime;
1122+ m_dPlayPosOld = m_dPlayPos;
1123+ m_dPlayPos = v;
1124+ m_iPlayPosTime = clock();
1125+ //m_playPosTime = QTime::currentTime();
1126+
1127+ m_iDupes = 0;
1128+ m_dPlayPosAdjust = 0;
1129+}
1130+
1131+void WaveformRenderer::slotUpdateRate(double v) {
1132+ m_dTargetRate = v;
1133+}
1134+
1135+void WaveformRenderer::slotUpdateRateRange(double v) {
1136+ m_dRateRange = v;
1137+}
1138+
1139+void WaveformRenderer::slotUpdateRateDir(double v) {
1140+ m_dRateDir = v;
1141+}
1142+
1143+void WaveformRenderer::slotUpdateLatency(double v) {
1144+ m_iLatency = v;
1145+}
1146+
1147+void WaveformRenderer::resize(int w, int h) {
1148+ m_iWidth = w;
1149+ m_iHeight = h;
1150+
1151+ setupControlObjects();
1152+
1153+ // Notify children that we've been resized
1154+ m_pRenderBackground->resize(w,h);
1155+ m_pRenderSignal->resize(w,h);
1156+ m_pRenderSignalPixmap->resize(w,h);
1157+ m_pRenderBeat->resize(w,h);
1158+
1159+ QListIterator<RenderObject*> iter(m_renderObjects);
1160+ while (iter.hasNext()) {
1161+ RenderObject* ro = iter.next();
1162+ ro->resize(w,h);
1163+ }
1164+}
1165+
1166+void WaveformRenderer::setupControlObjects() {
1167+
1168+ // the resample rate is the number of samples that correspond to one downsample
1169+
1170+ // This set of restrictions provides for a downsampling setup like this:
1171+
1172+ // Let a sample be a sample in the original song.
1173+ // Let a downsample be a sample in the downsampled buffer
1174+ // Let a pixel be a pixel on the screen.
1175+
1176+ // W samples -> X downsamples -> Y pixels
1177+
1178+ // We start with the restriction that we desire 1 second of
1179+ // 'raw' information to be contained within Z real pixels of screen space.
1180+
1181+ // 1) 1 second / z pixels = f samples / z pixels = (f/z) samples per pixel
1182+
1183+ // The size of the buffer we interact with is the number of downsamples
1184+
1185+ // The ratio of samples to downsamples is N : 1
1186+ // The ratio of downsamples to pixels is M : 1
1187+
1188+ // Therefore the ratio of samples to pixels is MN : 1
1189+
1190+ // Or in other words, we have MN samples per pixel
1191+
1192+ // 2) MN samples / pixel
1193+
1194+ // We combine 1 and 2 into one constraint:
1195+
1196+ // (f/z) = mn, or f = m * n * z
1197+
1198+ // REQUIRE : M * N * Z = F
1199+ // M : DOWNSAMPLES PER PIXEL
1200+ // N : SAMPLES PER DOWNSAMPLE
1201+ // F : SAMPLE RATE OF SONG
1202+ // Z : THE USER SEES 1 SECOND OF DATA IN Z PIXELS
1203+
1204+ // Solving for N, the number of samples in our downsample buffer,
1205+ // we get : N = F / (M*Z)
1206+
1207+ // We don't know F, so we're going to transmit M*Z
1208+
1209+ double m = m_iSubpixelsPerPixel; // M DOWNSAMPLES PER PIXEL
1210+ double z = m_iPixelsPerSecond; // Z PIXELS REPRESENTS 1 SECOND OF DATA
1211+
1212+ m_pCOVisualResample->set(m*z);
1213+
1214+ //qDebug() << "WaveformRenderer::setupControlObjects - VisualResample: " << m*z;
1215+
1216+}
1217+
1218+void WaveformRenderer::setup(QDomNode node) {
1219+
1220+ bgColor.setNamedColor(WWidget::selectNodeQString(node, "BgColor"));
1221+ bgColor = WSkinColor::getCorrectColor(bgColor);
1222+
1223+ signalColor.setNamedColor(WWidget::selectNodeQString(node, "SignalColor"));
1224+ signalColor = WSkinColor::getCorrectColor(signalColor);
1225+
1226+ colorMarker.setNamedColor(WWidget::selectNodeQString(node, "MarkerColor"));
1227+ colorMarker = WSkinColor::getCorrectColor(colorMarker);
1228+
1229+ colorBeat.setNamedColor(WWidget::selectNodeQString(node, "BeatColor"));
1230+ colorBeat = WSkinColor::getCorrectColor(colorBeat);
1231+
1232+ colorCue.setNamedColor(WWidget::selectNodeQString(node, "CueColor"));
1233+ colorCue = WSkinColor::getCorrectColor(colorCue);
1234+
1235+ while (m_renderObjects.size() > 0) {
1236+ RenderObject* ro = m_renderObjects.takeFirst();
1237+ delete ro;
1238+ }
1239+
1240+ // Process any <Mark> nodes
1241+ QDomNode child = node.firstChild();
1242+ while (!child.isNull()) {
1243+ RenderObject* pRenderObject = NULL;
1244+ if (child.nodeName() == "Mark") {
1245+ pRenderObject = new WaveformRenderMark(m_pGroup, this);
1246+ } else if(child.nodeName() == "MarkRange") {
1247+ pRenderObject = new WaveformRenderMarkRange(m_pGroup, this);
1248+ }
1249+ if (pRenderObject != NULL) {
1250+ if (m_pTrack != NULL)
1251+ pRenderObject->newTrack(m_pTrack);
1252+ pRenderObject->setup(child);
1253+ m_renderObjects.push_back(pRenderObject);
1254+ }
1255+ child = child.nextSibling();
1256+ }
1257+
1258+ m_pRenderBackground->setup(node);
1259+ m_pRenderSignal->setup(node);
1260+ m_pRenderSignalPixmap->setup(node);
1261+ m_pRenderBeat->setup(node);
1262+}
1263+
1264+
1265+void WaveformRenderer::precomputePixmap() {
1266+ if(m_pSampleBuffer == NULL || m_iNumSamples == 0 || !m_pImage.isNull())
1267+ return;
1268+
1269+ qDebug() << "Generating a image!";
1270+
1271+ int monoSamples = (m_iNumSamples >> 3);
1272+ qDebug() << monoSamples << " samples for qimage";
1273+ QImage qi(monoSamples, m_iHeight, QImage::Format_RGB32);
1274+
1275+ QPainter paint;
1276+ paint.begin(&qi);
1277+
1278+ paint.fillRect(qi.rect(), QBrush(QColor(255,0,0)));//bgColor));//QColor(0,0,0)));
1279+ paint.setPen(QColor(0,255,0));//signalColor);//QColor(0,255,0));
1280+ qDebug() << "height " << m_iHeight;
1281+ paint.translate(0,m_iHeight/2);
1282+ paint.scale(1.0,-1.0);
1283+ paint.drawLine(QLine(0,0,monoSamples,0));
1284+ //for (int i=0;i<100;i++) {
1285+ //paint.drawLine(QLine(i,0,i,m_iHeight/2));
1286+ //paint.drawLine(QLine(i,0,i,m_iHeight/2));
1287+ //}
1288+
1289+ for(int i=0;i<monoSamples;i++) {
1290+ //SAMPLE sampl = (*m_pSampleBuffer)[i*2];
1291+ //SAMPLE sampr = (*m_pSampleBuffer)[i*2+1];
1292+
1293+ //paint.drawLine(QLine(i,-5, i, 0));
1294+ paint.drawLine(QLine(i,2, i, 80));
1295+ //paint.drawLine(QLine(i,-sampr,i,sampl));
1296+ }
1297+ paint.end();
1298+ qDebug() << "done with image";
1299+ qi.save("/home/rryan/foo.bmp", "BMP", 100);
1300+ m_pImage = qi;
1301+
1302+
1303+ return;
1304+
1305+ /*
1306+ qDebug() << "Generating a pixmap!";
1307+
1308+ // Now generate a pixmap of this
1309+ QPixmap *pm = new QPixmap(m_iNumSamples/2, m_iHeight);
1310+
1311+ if(pm->isNull()) {
1312+ qDebug() << "Built a null pixmap, WTF!";
1313+ } else {
1314+ qDebug() << " Build a pixmap " << pm->size();
1315+ }
1316+
1317+ QPainter paint;
1318+ paint.begin(pm);
1319+
1320+ qDebug() << "Wave Precomp: BG: " << bgColor << " FG:" << signalColor;
1321+ paint.fillRect(pm->rect(), QBrush(bgColor));//QColor(0,0,0)));
1322+ paint.setPen(signalColor);//QColor(0,255,0));
1323+
1324+ paint.translate(0,m_iHeight/2);
1325+ paint.scale(1.0,-1.0);
1326+ //paint.drawLine(QLine(0,0,resultSamples/2,0));
1327+
1328+ for(int i=0;i<m_iNumSamples/2;i++) {
1329+ SAMPLE sampl = (*m_pSampleBuffer)[i*2];
1330+ SAMPLE sampr = (*m_pSampleBuffer)[i*2+1];
1331+
1332+ //paint.drawLine(QLine(i,-15, i, 15));
1333+ paint.drawLine(QLine(i,-sampr,i,sampl));
1334+ }
1335+ paint.end();
1336+
1337+ */
1338+}
1339+
1340+bool WaveformRenderer::fetchWaveformFromTrack() {
1341+
1342+ if(!m_pTrack)
1343+ return false;
1344+
1345+ QVector<float> *buffer = m_pTrack->getVisualWaveform();
1346+
1347+ if(buffer == NULL)
1348+ return false;
1349+
1350+ m_pSampleBuffer = buffer;
1351+ m_iNumSamples = buffer->size();
1352+
1353+ return true;
1354+}
1355+
1356+void WaveformRenderer::drawSignalPixmap(QPainter *pPainter) {
1357+
1358+
1359+ //if(m_pImage == NULL)
1360+ //return;
1361+ if(m_pImage.isNull())
1362+ return;
1363+
1364+ //double dCurPos = m_pPlayPos->get();
1365+ int iCurPos = (int)(m_dPlayPos*m_pImage.width());
1366+
1367+ int halfw = m_iWidth/2;
1368+ int halfh = m_iHeight/2;
1369+
1370+ int totalHeight = m_pImage.height();
1371+ int totalWidth = m_pImage.width();
1372+ int width = m_iWidth;
1373+ int height = m_iHeight;
1374+ // widths and heights of the two rects should be the same:
1375+ // m_iWidth - 0 = iCurPos + halfw - iCurPos + halfw = m_iWidth (if even)
1376+ // -halfh-halfh = -halfh-halfh
1377+
1378+ int sx=iCurPos-halfw;
1379+ int sy=0;
1380+ int tx=0;
1381+ int ty=0;
1382+
1383+ if(sx < 0) {
1384+ sx = 0;
1385+ width = iCurPos + halfw;
1386+ tx = m_iWidth - width;
1387+ } else if(sx + width >= totalWidth) {
1388+ //width = (iCurPos - sx) + (totalWidth-iCurPos);
1389+ width = halfw + totalWidth - iCurPos;
1390+ }
1391+
1392+ QRect target(tx,ty,width,height);
1393+ QRect source(sx,sy,width,height);
1394+
1395+ //qDebug() << "target:" << target;
1396+ //qDebug() << "source:" << source;
1397+ pPainter->setPen(signalColor);
1398+
1399+ pPainter->drawImage(target, m_pImage, source);
1400+
1401+}
1402+
1403+void WaveformRenderer::draw(QPainter* pPainter, QPaintEvent *pEvent) {
1404+ double playposadjust = 0;
1405+
1406+ if(m_iWidth == 0 || m_iHeight == 0)
1407+ return;
1408+
1409+
1410+ /*
1411+ if(m_dPlayPos != -1 && m_dPlayPosOld != -1 && m_iNumSamples != 0) {
1412+ static double elatency = ControlObject::getControl(ConfigKey("[Master]","latency"))->get();
1413+ double latency = elatency;
1414+ latency *= 4;
1415+ latency *= CLOCKS_PER_SEC / 1000.0;
1416+
1417+ //int latency = m_iPlayPosTime - m_iPlayPosTimeOld;
1418+ double timeelapsed = (clock() - m_iPlayPosTime);
1419+ double timeratio = 0;
1420+ if(latency != 0)
1421+ timeratio = double(timeelapsed) / double(latency);
1422+ if(timeratio > 1.0)
1423+ timeratio = 1.0;
1424+
1425+ double timerun = m_iPlayPosTime - m_iPlayPosTimeOld;
1426+
1427+
1428+ playposadjust = ((m_dPlayPos*m_iNumSamples) - (m_dPlayPosOld*m_iNumSamples)) * timeelapsed;
1429+ playposadjust /= (latency*m_iNumSamples);
1430+
1431+ //qDebug() << m_dPlayPos - m_dPlayPosOld << " " << timerun;
1432+
1433+ //qDebug() << "ppold " << m_dPlayPosOld << " pp " << m_dPlayPos << " timeratio " << timeratio;
1434+ //qDebug() << "timee" << timeelapsed << "playpoadj" << playposadjust;
1435+ }
1436+ */
1437+ m_iDupes++;
1438+
1439+ double playpos = m_dPlayPos + m_dPlayPosAdjust;
1440+
1441+ //qDebug() << m_dPlayPosAdjust;
1442+
1443+ // Gradually stretch the waveform
1444+ if (fabs(m_dTargetRate - m_dRate) > RATE_INCREMENT)
1445+ {
1446+ if ((m_dTargetRate - m_dRate) > 0)
1447+ {
1448+ m_iRateAdjusting = m_iRateAdjusting > 0 ? m_iRateAdjusting + 1 : 1;
1449+ m_dRate = math_min(m_dTargetRate, m_dRate + RATE_INCREMENT * pow(
1450+ static_cast<double>(m_iRateAdjusting), 2) / 80);
1451+ }
1452+ else
1453+ {
1454+ m_iRateAdjusting = m_iRateAdjusting < 0 ? m_iRateAdjusting - 1 : -1;
1455+ m_dRate = math_max(m_dTargetRate, m_dRate - RATE_INCREMENT * pow(
1456+ static_cast<double>(m_iRateAdjusting), 2) / 80);
1457+ }
1458+ }
1459+ else
1460+ {
1461+ m_iRateAdjusting = 0;
1462+ m_dRate = m_dTargetRate;
1463+ }
1464+
1465+ // Limit our rate adjustment to < 99%, "Bad Things" might happen otherwise.
1466+ double rateAdjust = m_dRateDir * math_min(0.99, m_dRate * m_dRateRange);
1467+
1468+ if(m_pSampleBuffer == NULL) {
1469+ fetchWaveformFromTrack();
1470+ }
1471+
1472+ m_pRenderBackground->draw(pPainter, pEvent, m_pSampleBuffer, playpos, rateAdjust);
1473+
1474+ pPainter->setPen(signalColor);
1475+
1476+ //m_pRenderSignalPixmap->draw(pPainter, pEvent, m_pSampleBuffer, playpos, rateAdjust);
1477+ // Translate our coordinate frame from (0,0) at top left
1478+ // to (0,0) at left, center. All the subrenderers expect this.
1479+ pPainter->translate(0.0,m_iHeight/2.0);
1480+
1481+ // Now scale so that positive-y points up.
1482+ pPainter->scale(1.0,-1.0);
1483+
1484+ // Draw the center horizontal line under the signal.
1485+ pPainter->drawLine(QLine(0,0,m_iWidth,0));
1486+
1487+ m_pRenderSignal->draw(pPainter, pEvent, m_pSampleBuffer, playpos, rateAdjust);
1488+
1489+ // Draw various markers.
1490+ m_pRenderBeat->draw(pPainter, pEvent, m_pSampleBuffer, playpos, rateAdjust);
1491+
1492+ QListIterator<RenderObject*> iter(m_renderObjects);
1493+ while (iter.hasNext()) {
1494+ RenderObject* ro = iter.next();
1495+ ro->draw(pPainter, pEvent, m_pSampleBuffer, playpos, rateAdjust);
1496+ }
1497+
1498+ pPainter->setPen(colorMarker);
1499+
1500+ // Draw the center vertical line
1501+ pPainter->drawLine(QLineF(m_iWidth/2.0,m_iHeight/2.0,m_iWidth/2.0,-m_iHeight/2.0));
1502+
1503+}
1504+
1505+void WaveformRenderer::slotUnloadTrack(TrackPointer pTrack) {
1506+ // All RenderObject's must support newTrack() calls with NULL
1507+ slotNewTrack(TrackPointer());
1508+}
1509+
1510+void WaveformRenderer::slotNewTrack(TrackPointer pTrack) {
1511+
1512+ m_pTrack = pTrack;
1513+ m_pSampleBuffer = NULL;
1514+ m_iNumSamples = 0;
1515+ m_dPlayPos = 0;
1516+ m_dPlayPosOld = 0;
1517+
1518+ m_pRenderBackground->newTrack(pTrack);
1519+ m_pRenderSignal->newTrack(pTrack);
1520+ m_pRenderSignalPixmap->newTrack(pTrack);
1521+ m_pRenderBeat->newTrack(pTrack);
1522+
1523+ QListIterator<RenderObject*> iter(m_renderObjects);
1524+ while (iter.hasNext()) {
1525+ RenderObject* ro = iter.next();
1526+ ro->newTrack(pTrack);
1527+ }
1528+}
1529+
1530+double WaveformRenderer::getPixelsPerSecond() {
1531+ return m_iPixelsPerSecond;
1532+}
1533+
1534+double WaveformRenderer::getSubpixelsPerPixel() {
1535+ return m_iSubpixelsPerPixel;
1536+}
1537
1538=== added file 'mixxx/src/waveform/waveformrenderer.h.OTHER'
1539--- mixxx/src/waveform/waveformrenderer.h.OTHER 1970-01-01 00:00:00 +0000
1540+++ mixxx/src/waveform/waveformrenderer.h.OTHER 2012-05-17 16:06:19 +0000
1541@@ -0,0 +1,96 @@
1542+
1543+#ifndef WAVEFORMRENDERER_H
1544+#define WAVEFORMRENDERER_H
1545+
1546+#include <QColor>
1547+#include <QDomNode>
1548+#include <QList>
1549+#include <QMutex>
1550+#include <QPainter>
1551+#include <QPaintEvent>
1552+#include <QThread>
1553+#include <QTime>
1554+#include <QVector>
1555+
1556+#include "defs.h"
1557+#include "trackinfoobject.h"
1558+
1559+class ControlObjectThreadMain;
1560+class RenderObject;
1561+class WaveformRenderBackground;
1562+class WaveformRenderSignal;
1563+class WaveformRenderSignalPixmap;
1564+class WaveformRenderBeat;
1565+class WaveformRenderMark;
1566+class ControlObject;
1567+
1568+class WaveformRenderer : public QThread {
1569+ Q_OBJECT
1570+public:
1571+ WaveformRenderer(const char* group);
1572+ virtual ~WaveformRenderer();
1573+
1574+ void resize(int w, int h);
1575+ void draw(QPainter* pPainter, QPaintEvent *pEvent);
1576+ void drawSignalPixmap(QPainter* p);
1577+ void setup(QDomNode node);
1578+ void precomputePixmap();
1579+ double getSubpixelsPerPixel();
1580+ double getPixelsPerSecond();
1581+
1582+public slots:
1583+ void slotNewTrack(TrackPointer pTrack);
1584+ void slotUnloadTrack(TrackPointer pTrack);
1585+ void slotUpdateLatency(double latency);
1586+ void slotUpdatePlayPos(double playpos);
1587+ void slotUpdateRate(double rate);
1588+ void slotUpdateRateRange(double rate_range);
1589+ void slotUpdateRateDir(double rate_dir);
1590+
1591+protected:
1592+ void run();
1593+
1594+private:
1595+ void setupControlObjects();
1596+ bool fetchWaveformFromTrack();
1597+
1598+ const char* m_pGroup;
1599+ int m_iWidth, m_iHeight;
1600+ QColor bgColor, signalColor, colorMarker, colorBeat, colorCue;
1601+ int m_iNumSamples;
1602+
1603+ int m_iPlayPosTime, m_iPlayPosTimeOld;
1604+ QTime m_playPosTime, m_playPosTimeOld;
1605+ double m_dPlayPos, m_dPlayPosOld, m_dTargetRate, m_dRate, m_dRateRange, m_dRateDir;
1606+ int m_iRateAdjusting;
1607+ int m_iDupes;
1608+ double m_dPlayPosAdjust;
1609+ int m_iLatency;
1610+
1611+ QVector<float> *m_pSampleBuffer;
1612+ QPixmap *m_pPixmap;
1613+ QImage m_pImage;
1614+
1615+ ControlObjectThreadMain *m_pLatency;
1616+ ControlObjectThreadMain *m_pPlayPos;
1617+ ControlObjectThreadMain *m_pRate;
1618+ ControlObjectThreadMain *m_pRateRange;
1619+ ControlObjectThreadMain *m_pRateDir;
1620+
1621+ ControlObject *m_pCOVisualResample;
1622+
1623+ WaveformRenderBackground *m_pRenderBackground;
1624+ WaveformRenderSignal *m_pRenderSignal;
1625+ WaveformRenderSignalPixmap *m_pRenderSignalPixmap;
1626+ WaveformRenderBeat *m_pRenderBeat;
1627+
1628+ QList<RenderObject*> m_renderObjects;
1629+
1630+ const double m_iSubpixelsPerPixel;
1631+ const double m_iPixelsPerSecond;
1632+ TrackPointer m_pTrack;
1633+
1634+ bool m_bQuit;
1635+};
1636+
1637+#endif
1638
1639=== added file 'mixxx/src/waveform/waveformrendersignal.cpp.OTHER'
1640--- mixxx/src/waveform/waveformrendersignal.cpp.OTHER 1970-01-01 00:00:00 +0000
1641+++ mixxx/src/waveform/waveformrendersignal.cpp.OTHER 2012-05-17 16:06:19 +0000
1642@@ -0,0 +1,153 @@
1643+#include <QDebug>
1644+#include <QColor>
1645+#include <QDomNode>
1646+#include <QPaintEvent>
1647+#include <QPainter>
1648+#include <QObject>
1649+#include <QVector>
1650+#include <QLine>
1651+#include <qgl.h>
1652+
1653+#include "waveformrendersignal.h"
1654+#include "waveformrenderer.h"
1655+#include "controlobjectthreadmain.h"
1656+#include "controlobject.h"
1657+#include "widget/wskincolor.h"
1658+#include "widget/wwidget.h"
1659+#include "trackinfoobject.h"
1660+
1661+WaveformRenderSignal::WaveformRenderSignal(const char* group, WaveformRenderer *parent)
1662+ : m_pParent(parent),
1663+ m_iWidth(0),
1664+ m_iHeight(0),
1665+ m_fGain(1),
1666+ m_lines(0),
1667+ m_pTrack(NULL),
1668+ signalColor(255,255,255) {
1669+ m_pGain = new ControlObjectThreadMain(
1670+ ControlObject::getControl(ConfigKey(group, "total_gain")));
1671+ if (m_pGain != NULL) {
1672+ connect(m_pGain, SIGNAL(valueChanged(double)),
1673+ this, SLOT(slotUpdateGain(double)));
1674+ }
1675+}
1676+
1677+WaveformRenderSignal::~WaveformRenderSignal() {
1678+ qDebug() << this << "~WaveformRenderSignal()";
1679+ delete m_pGain;
1680+}
1681+
1682+void WaveformRenderSignal::resize(int w, int h) {
1683+ m_iWidth = w;
1684+ m_iHeight = h;
1685+}
1686+
1687+void WaveformRenderSignal::newTrack(TrackPointer pTrack) {
1688+ m_pTrack = pTrack;
1689+}
1690+
1691+void WaveformRenderSignal::slotUpdateGain(double v) {
1692+ m_fGain = v;
1693+}
1694+
1695+void WaveformRenderSignal::setup(QDomNode node) {
1696+ signalColor.setNamedColor(WWidget::selectNodeQString(node, "SignalColor"));
1697+ signalColor = WSkinColor::getCorrectColor(signalColor);
1698+}
1699+
1700+
1701+void WaveformRenderSignal::draw(QPainter *pPainter, QPaintEvent *event,
1702+ QVector<float> *buffer, double dPlayPos,
1703+ double rateAdjust) {
1704+ if (buffer == NULL) {
1705+ return;
1706+ }
1707+
1708+ const float* baseBuffer = buffer->constData();
1709+
1710+ int numBufferSamples = buffer->size();
1711+ int iCurPos = 0;
1712+ iCurPos = (int)(dPlayPos*numBufferSamples);
1713+
1714+ if ((iCurPos % 2) != 0)
1715+ iCurPos--;
1716+
1717+ pPainter->save();
1718+
1719+ pPainter->setPen(signalColor);
1720+ pPainter->setBrush(signalColor);
1721+
1722+ const double subpixelsPerPixel = m_pParent->getSubpixelsPerPixel() * (1.0 + rateAdjust);
1723+
1724+ int subpixelWidth = int(m_iWidth * subpixelsPerPixel);
1725+
1726+ // If the array is not large enough, expand it.
1727+ // Amortize the cost of this by requesting a factor of 2 more.
1728+ // if(m_lines.size() < subpixelWidth) {
1729+ // m_lines.resize(2*subpixelWidth);
1730+ // }
1731+
1732+ // Use the pointer to the QVector internal data to avoid range
1733+ // checks. QVector<QLineF>::operator[] profiled very high. const_cast is
1734+ // naughty but we just want Qt to leave us alone here. WARNING: calling
1735+ // m_lines.data() will copy the entire vector in memory by calling
1736+ // QVector<T>::detach(). QVector<T>::constData() does not do this.
1737+#if 0
1738+ QLineF* lineData = const_cast<QLineF*>(m_lines.constData());
1739+ int halfw = subpixelWidth/2;
1740+ for(int i=0;i<subpixelWidth;i++) {
1741+ // Start at curPos minus half the waveform viewer
1742+ int thisIndex = iCurPos+2*(i-halfw);
1743+ if(thisIndex >= 0 && (thisIndex+1) < numBufferSamples) {
1744+ float sampl = baseBuffer[thisIndex] * m_fGain * m_iHeight * 0.5f;
1745+ float sampr = -baseBuffer[thisIndex+1] * m_fGain * m_iHeight * 0.5f;
1746+ const qreal xPos = i/subpixelsPerPixel;
1747+ lineData[i].setLine(xPos, sampr, xPos, sampl);
1748+ } else {
1749+ lineData[i].setLine(0,0,0,0);
1750+ }
1751+ }
1752+
1753+ // Only draw lines that we have provided
1754+ pPainter->drawLines(lineData, subpixelWidth);
1755+#endif
1756+
1757+ QPointF points[subpixelWidth*2];
1758+ int halfw = subpixelWidth/2;
1759+ for(int i=0;i<subpixelWidth;i++) {
1760+ // Start at curPos minus half the waveform viewer
1761+ int thisIndex = iCurPos+2*(i-halfw);
1762+ if(thisIndex >= 0 && (thisIndex+1) < numBufferSamples) {
1763+ float sampl = baseBuffer[thisIndex] * m_fGain * m_iHeight * 0.5f;
1764+ float sampr = -baseBuffer[thisIndex+1] * m_fGain * m_iHeight * 0.5f;
1765+ const qreal xPos = i/subpixelsPerPixel;
1766+ points[i]=QPointF(xPos, sampl);
1767+ points[subpixelWidth*2-i-1]=QPointF(xPos, sampr);
1768+ } else {
1769+ points[i]=QPointF(0,0);
1770+ points[subpixelWidth*2-i-1]=QPointF(0,0);
1771+ }
1772+ }
1773+ pPainter->drawPolyline(points, subpixelWidth*2);
1774+
1775+ // Some of the pre-roll is on screen. Draw little triangles to indicate
1776+ // where the pre-roll is located.
1777+ if (iCurPos < 2*halfw) {
1778+ double start_index = 0;
1779+ int end_index = (halfw - iCurPos/2);
1780+ QPolygonF polygon;
1781+ const int polyWidth = 80;
1782+ polygon << QPointF(0, 0)
1783+ << QPointF(-polyWidth/subpixelsPerPixel, -m_iHeight/5)
1784+ << QPointF(-polyWidth/subpixelsPerPixel, m_iHeight/5);
1785+ polygon.translate(end_index/subpixelsPerPixel, 0);
1786+
1787+ int index = end_index;
1788+ while (index > start_index) {
1789+ pPainter->drawPolygon(polygon);
1790+ polygon.translate(-polyWidth/subpixelsPerPixel, 0);
1791+ index -= polyWidth;
1792+ }
1793+ }
1794+ pPainter->restore();
1795+}