Merge lp:~raskolnikov/mixxx/round-bpm into lp:~mixxxdevelopers/mixxx/trunk
- round-bpm
- Merge into trunk
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 | ||||
Related bugs: |
|
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 |
Commit message
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.
Sean M. Pappalardo (pegasus-renegadetech) wrote : | # |
- 2596. By raskolnikov
-
Merged from trunk.
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/
in case you need an example.
* The file src/dlgprefmidi
This file has file markers indicative of an unresolved conflict.
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.
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
- 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.
raskolnikov (raskolnikov) wrote : | # |
I think we can drop this branch given the accurate results of the new BPM editor.
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
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 | +} |
I committed your midi prefs dialog fix to trunk r2601, so you'll want to revert them in this branch then merge from trunk.