Merge lp:~mixxxdevelopers/mixxx/features_cuefile into lp:~mixxxdevelopers/mixxx/trunk
- features_cuefile
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 2699 |
Proposed branch: | lp:~mixxxdevelopers/mixxx/features_cuefile |
Merge into: | lp:~mixxxdevelopers/mixxx/trunk |
Diff against target: |
1231 lines (+552/-272) 9 files modified
mixxx/src/dlgprefrecord.cpp (+43/-0) mixxx/src/dlgprefrecord.h (+3/-0) mixxx/src/dlgprefrecorddlg.ui (+57/-0) mixxx/src/engine/engineshoutcast.cpp (+170/-251) mixxx/src/engine/engineshoutcast.h (+1/-4) mixxx/src/playerinfo.cpp (+97/-8) mixxx/src/playerinfo.h (+11/-0) mixxx/src/recording/enginerecord.cpp (+151/-9) mixxx/src/recording/enginerecord.h (+19/-0) |
To merge this branch: | bzr merge lp:~mixxxdevelopers/mixxx/features_cuefile |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
RJ Skerry-Ryan | Approve | ||
Review via email:
|
Commit message
Description of the change
This adds support for generating CUE files when recording. This file can be used to split the recording or to generate playlists.
- 2626. By Phillip Whelan
-
Merging from Trunk.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
RAFFI TEA (raffitea) wrote : | # |
Hey Philip,
there's a singleton PlayerInfo class that EngineShoutcast uses to get track information per deck. Maybe you can extract metadata recording in there. This would be of great benefit for the library history feature, too.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Phillip Whelan (pwhelan) wrote : | # |
Right now I am moving the 'Current Track' functionality into PlayerInfo.
I was planning on using a timestamp to make sure it updates at most once per X seconds. If another call is made within that timeframe it returns the last answer it made.
- 2627. By Phillip Whelan
-
Move all the Current Deck code into PlayerInfo. Make it N-deck aware.
* Add the getCurrentPlayi
ngDeck method to PlayerInfo:
Returns the deck number of the loudest active deck.
* Add the getCurrentPlayingTrack method to PlayerInfo.
* Refactor EngineRecord to query PlayerInfo for the current track.
* Refactor EngineShoutCast to query Playerinfo for the current track.
* Change instances of m_pMetadataLife to m_iMetadataLife. - 2628. By Phillip Whelan
-
Make getCurrentPlayi
ngDeck method thread safe with a Mutex. getCurrentPlayi ngTrack does not need a Mutex since it calls mutex'd methods and uses no other state itself.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Phillip Whelan (pwhelan) wrote : | # |
I moved all the functionality into PlayefInfo and refactored EngineRecord and EngineShoutCast to use it.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
RJ Skerry-Ryan (rryan) wrote : | # |
Awesome -- looks good for merging. I found these two issues:
1) TrackInfoObject
2) I tried out the cuefile recording and it didn't pick up 2 out of the 4 tracks I played. I think the problem is the math in PlayerInfo:
This value for the xfval calculation worked for me (all 6 tracks I played got recorded in the correct order):
xfvol = 1 - 0.5 * fabs(m_
It maps tracks opposite from each other (e.g. oriented right crossfader left and vice-versa) to 0.0, and tracks that are exactly aligned w/ the crossfader (left/left, center/center, or right/right) to 1.0.
- 2629. By Phillip Whelan
-
FIX: get xfader code to work orientation values; left = 0, right = 2.
- 2630. By Phillip Whelan
-
FIX: take into account that TrackInfoObject
->getId( ) == -1 for external track sources.
Preview Diff
1 | === modified file 'mixxx/src/dlgprefrecord.cpp' |
2 | --- mixxx/src/dlgprefrecord.cpp 2010-11-01 17:24:48 +0000 |
3 | +++ mixxx/src/dlgprefrecord.cpp 2011-03-25 05:39:41 +0000 |
4 | @@ -55,11 +55,14 @@ |
5 | |
6 | //Connections |
7 | connect(PushButtonBrowse, SIGNAL(clicked()), this, SLOT(slotBrowseSave())); |
8 | + connect(PushButtonBrowseCue, SIGNAL(clicked()), this, SLOT(slotBrowseCueSave())); |
9 | connect(LineEditRecPath, SIGNAL(returnPressed()), this, SLOT(slotApply())); |
10 | connect(comboBoxEncoding, SIGNAL(activated(int)), this, SLOT(slotRecordPathChange())); |
11 | connect(SliderQuality, SIGNAL(valueChanged(int)), this, SLOT(slotSliderQuality())); |
12 | connect(SliderQuality, SIGNAL(sliderMoved(int)), this, SLOT(slotSliderQuality())); |
13 | connect(SliderQuality, SIGNAL(sliderReleased()), this, SLOT(slotSliderQuality())); |
14 | + connect(CheckBoxRecordCueFile, SIGNAL(stateChanged(int)), this, SLOT(slotEnableCueFile(int))); |
15 | + connect(LineEditRecPath, SIGNAL(textChanged(QString)), this, SLOT(slotRecordPathChanged(QString))); |
16 | |
17 | slotApply(); |
18 | recordControl->slotSet(RECORD_OFF); //make sure a corrupt config file won't cause us to record constantly |
19 | @@ -80,6 +83,45 @@ |
20 | } |
21 | } |
22 | |
23 | +void DlgPrefRecord::slotBrowseCueSave() |
24 | +{ |
25 | + QString encodingFileFilter = QString("CUE file (*.cue)"); |
26 | + QString selectedFile = QFileDialog::getSaveFileName(NULL, tr("Save Cue File As..."), config->getValueString(ConfigKey(RECORDING_PREF_KEY,"CuePath")), encodingFileFilter); |
27 | + if (selectedFile.toLower() != "") |
28 | + { |
29 | + if(!selectedFile.toLower().endsWith(".cue")) |
30 | + { |
31 | + selectedFile.append(".cue"); |
32 | + } |
33 | + LineEditCuePath->setText( selectedFile ); |
34 | + } |
35 | +} |
36 | + |
37 | +void DlgPrefRecord::slotEnableCueFile(int enabled) |
38 | +{ |
39 | + bool status = enabled ? true : false; |
40 | + |
41 | + |
42 | + config->set(ConfigKey(RECORDING_PREF_KEY, "CueEnabled"), ConfigValue(CheckBoxRecordCueFile->isChecked())); |
43 | + |
44 | + LabelCueFile->setEnabled(status); |
45 | + LineEditCuePath->setEnabled(status); |
46 | + PushButtonBrowseCue->setEnabled(status); |
47 | +} |
48 | + |
49 | +void DlgPrefRecord::slotRecordPathChanged(QString path) |
50 | +{ |
51 | + QString cuePath = path; |
52 | + int pos; |
53 | + |
54 | + |
55 | + pos = path.lastIndexOf("."); |
56 | + cuePath.replace(pos, 4, ".cue"); |
57 | + cuePath.truncate(pos + 4); |
58 | + |
59 | + LineEditCuePath->setText(cuePath); |
60 | +} |
61 | + |
62 | void DlgPrefRecord::slotSliderQuality() |
63 | { |
64 | updateTextQuality(); |
65 | @@ -192,6 +234,7 @@ |
66 | void DlgPrefRecord::slotApply() |
67 | { |
68 | config->set(ConfigKey(RECORDING_PREF_KEY, "Path"), LineEditRecPath->text()); |
69 | + config->set(ConfigKey(RECORDING_PREF_KEY, "CuePath"), LineEditCuePath->text()); |
70 | setMetaData(); |
71 | |
72 | slotEncoding(); |
73 | |
74 | === modified file 'mixxx/src/dlgprefrecord.h' |
75 | --- mixxx/src/dlgprefrecord.h 2008-03-20 03:42:11 +0000 |
76 | +++ mixxx/src/dlgprefrecord.h 2011-03-25 05:39:41 +0000 |
77 | @@ -41,6 +41,9 @@ |
78 | void slotApply(); |
79 | void slotUpdate(); |
80 | void slotBrowseSave(); |
81 | + void slotBrowseCueSave(); |
82 | + void slotEnableCueFile(int); |
83 | + void slotRecordPathChanged(QString); |
84 | void slotEncoding(); |
85 | void slotSliderQuality(); |
86 | void slotRecordPathChange(); |
87 | |
88 | === modified file 'mixxx/src/dlgprefrecorddlg.ui' |
89 | --- mixxx/src/dlgprefrecorddlg.ui 2010-11-29 21:23:11 +0000 |
90 | +++ mixxx/src/dlgprefrecorddlg.ui 2011-03-25 05:39:41 +0000 |
91 | @@ -239,6 +239,63 @@ |
92 | </widget> |
93 | </item> |
94 | <item> |
95 | + <widget class="QCheckBox" name="CheckBoxRecordCueFile"> |
96 | + <property name="text"> |
97 | + <string>Create a CUE file</string> |
98 | + </property> |
99 | + </widget> |
100 | + </item> |
101 | + <item> |
102 | + <layout class="QHBoxLayout" name="horizontalLayout"> |
103 | + <item> |
104 | + <widget class="QLabel" name="LabelCueFile"> |
105 | + <property name="enabled"> |
106 | + <bool>false</bool> |
107 | + </property> |
108 | + <property name="sizePolicy"> |
109 | + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> |
110 | + <horstretch>0</horstretch> |
111 | + <verstretch>0</verstretch> |
112 | + </sizepolicy> |
113 | + </property> |
114 | + <property name="text"> |
115 | + <string>Cue File</string> |
116 | + </property> |
117 | + </widget> |
118 | + </item> |
119 | + <item> |
120 | + <spacer name="horizontalSpacer"> |
121 | + <property name="orientation"> |
122 | + <enum>Qt::Horizontal</enum> |
123 | + </property> |
124 | + <property name="sizeHint" stdset="0"> |
125 | + <size> |
126 | + <width>40</width> |
127 | + <height>20</height> |
128 | + </size> |
129 | + </property> |
130 | + </spacer> |
131 | + </item> |
132 | + <item> |
133 | + <widget class="QLineEdit" name="LineEditCuePath"> |
134 | + <property name="enabled"> |
135 | + <bool>false</bool> |
136 | + </property> |
137 | + </widget> |
138 | + </item> |
139 | + <item> |
140 | + <widget class="QPushButton" name="PushButtonBrowseCue"> |
141 | + <property name="enabled"> |
142 | + <bool>false</bool> |
143 | + </property> |
144 | + <property name="text"> |
145 | + <string>Browse...</string> |
146 | + </property> |
147 | + </widget> |
148 | + </item> |
149 | + </layout> |
150 | + </item> |
151 | + <item> |
152 | <spacer> |
153 | <property name="orientation"> |
154 | <enum>Qt::Vertical</enum> |
155 | |
156 | === modified file 'mixxx/src/engine/engineshoutcast.cpp' |
157 | --- mixxx/src/engine/engineshoutcast.cpp 2010-11-04 18:32:26 +0000 |
158 | +++ mixxx/src/engine/engineshoutcast.cpp 2011-03-25 05:39:41 +0000 |
159 | @@ -27,9 +27,9 @@ |
160 | #include "playerinfo.h" |
161 | #include "trackinfoobject.h" |
162 | #ifdef __WINDOWS__ |
163 | - #include <windows.h> |
164 | - //sleep on linux assumes seconds where as Sleep on Windows assumes milliseconds |
165 | - #define sleep(x) Sleep(x*1000) |
166 | + #include <windows.h> |
167 | + //sleep on linux assumes seconds where as Sleep on Windows assumes milliseconds |
168 | + #define sleep(x) Sleep(x*1000) |
169 | #endif |
170 | |
171 | #define TIMEOUT 10 |
172 | @@ -49,9 +49,6 @@ |
173 | m_pConfig(_config), |
174 | m_encoder(NULL), |
175 | m_pUpdateShoutcastFromPrefs(NULL), |
176 | - m_pCrossfader(NULL), |
177 | - m_pVolume1(NULL), |
178 | - m_pVolume2(NULL), |
179 | m_shoutMutex(QMutex::Recursive) { |
180 | |
181 | m_pShout = 0; |
182 | @@ -61,12 +58,6 @@ |
183 | |
184 | m_bQuit = false; |
185 | |
186 | - m_pCrossfader = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Master]","crossfader"))); |
187 | - m_pVolume1 = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Channel1]","volume"))); |
188 | - m_pVolume2 = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Channel2]","volume"))); |
189 | - |
190 | - |
191 | - |
192 | m_firstCall = false; |
193 | // Initialize libshout |
194 | shout_init(); |
195 | @@ -94,16 +85,13 @@ |
196 | QMutexLocker locker(&m_shoutMutex); |
197 | |
198 | if (m_encoder){ |
199 | - m_encoder->flush(); |
200 | + m_encoder->flush(); |
201 | |
202 | - delete m_encoder; |
203 | - } |
204 | + delete m_encoder; |
205 | + } |
206 | |
207 | delete m_pUpdateShoutcastFromPrefs; |
208 | delete m_pShoutcastNeedUpdateFromPrefs; |
209 | - delete m_pCrossfader; |
210 | - delete m_pVolume1; |
211 | - delete m_pVolume2; |
212 | |
213 | if (m_pShoutMetaData) |
214 | shout_metadata_free(m_pShoutMetaData); |
215 | @@ -117,26 +105,26 @@ |
216 | { |
217 | QMutexLocker locker(&m_shoutMutex); |
218 | if (m_encoder){ |
219 | - m_encoder->flush(); |
220 | - delete m_encoder; |
221 | - m_encoder = NULL; |
222 | - } |
223 | + m_encoder->flush(); |
224 | + delete m_encoder; |
225 | + m_encoder = NULL; |
226 | + } |
227 | |
228 | if (m_pShout) { |
229 | shout_close(m_pShout); |
230 | - return true; |
231 | + return true; |
232 | } |
233 | - return false; //if no connection has been established, nothing can be disconnected |
234 | + return false; //if no connection has been established, nothing can be disconnected |
235 | } |
236 | bool EngineShoutcast::isConnected() |
237 | { |
238 | QMutexLocker locker(&m_shoutMutex); |
239 | - if (m_pShout) { |
240 | - m_iShoutStatus = shout_get_connected(m_pShout); |
241 | - if (m_iShoutStatus == SHOUTERR_CONNECTED) |
242 | - return true; |
243 | - } |
244 | - return false; |
245 | + if (m_pShout) { |
246 | + m_iShoutStatus = shout_get_connected(m_pShout); |
247 | + if (m_iShoutStatus == SHOUTERR_CONNECTED) |
248 | + return true; |
249 | + } |
250 | + return false; |
251 | } |
252 | /* |
253 | * Update EngineShoutcast values from the preferences. |
254 | @@ -162,11 +150,11 @@ |
255 | QByteArray baStreamPublic = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"stream_public")).toLatin1(); |
256 | QByteArray baBitrate = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"bitrate")).toLatin1(); |
257 | |
258 | - m_baFormat = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"format")).toLatin1(); |
259 | + m_baFormat = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"format")).toLatin1(); |
260 | |
261 | - m_custom_metadata = (bool)m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"enable_metadata")).toInt(); |
262 | - m_baCustom_title = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_title")).toLatin1(); |
263 | - m_baCustom_artist = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_artist")).toLatin1(); |
264 | + m_custom_metadata = (bool)m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"enable_metadata")).toInt(); |
265 | + m_baCustom_title = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_title")).toLatin1(); |
266 | + m_baCustom_artist = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_artist")).toLatin1(); |
267 | |
268 | int format; |
269 | int len; |
270 | @@ -174,22 +162,22 @@ |
271 | |
272 | |
273 | if (shout_set_host(m_pShout, baHost.data()) != SHOUTERR_SUCCESS) { |
274 | - errorDialog("Error setting hostname!", shout_get_error(m_pShout)); |
275 | + errorDialog("Error setting hostname!", shout_get_error(m_pShout)); |
276 | return; |
277 | } |
278 | |
279 | if (shout_set_protocol(m_pShout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { |
280 | - errorDialog("Error setting protocol!", shout_get_error(m_pShout)); |
281 | + errorDialog("Error setting protocol!", shout_get_error(m_pShout)); |
282 | return; |
283 | } |
284 | |
285 | if (shout_set_port(m_pShout, baPort.toUInt()) != SHOUTERR_SUCCESS) { |
286 | - errorDialog("Error setting port!", shout_get_error(m_pShout)); |
287 | + errorDialog("Error setting port!", shout_get_error(m_pShout)); |
288 | return; |
289 | } |
290 | |
291 | if (shout_set_password(m_pShout, baPassword.data()) != SHOUTERR_SUCCESS) { |
292 | - errorDialog("Error setting password!", shout_get_error(m_pShout)); |
293 | + errorDialog("Error setting password!", shout_get_error(m_pShout)); |
294 | return; |
295 | } |
296 | if (shout_set_mount(m_pShout, baMountPoint.data()) != SHOUTERR_SUCCESS) { |
297 | @@ -198,22 +186,22 @@ |
298 | } |
299 | |
300 | if (shout_set_user(m_pShout, baLogin.data()) != SHOUTERR_SUCCESS) { |
301 | - errorDialog("Error setting username!", shout_get_error(m_pShout)); |
302 | - return; |
303 | - } |
304 | - if (shout_set_name(m_pShout, baStreamName.data()) != SHOUTERR_SUCCESS) { |
305 | - errorDialog("Error setting stream name!", shout_get_error(m_pShout)); |
306 | - return; |
307 | - } |
308 | - if (shout_set_description(m_pShout, baStreamDesc.data()) != SHOUTERR_SUCCESS) { |
309 | - errorDialog("Error setting stream description!", shout_get_error(m_pShout)); |
310 | - return; |
311 | - } |
312 | - if (shout_set_genre(m_pShout, baStreamGenre.data()) != SHOUTERR_SUCCESS) { |
313 | - errorDialog("Error setting stream genre!", shout_get_error(m_pShout)); |
314 | - return; |
315 | - } |
316 | - if (shout_set_url(m_pShout, baStreamWebsite.data()) != SHOUTERR_SUCCESS) { |
317 | + errorDialog("Error setting username!", shout_get_error(m_pShout)); |
318 | + return; |
319 | + } |
320 | + if (shout_set_name(m_pShout, baStreamName.data()) != SHOUTERR_SUCCESS) { |
321 | + errorDialog("Error setting stream name!", shout_get_error(m_pShout)); |
322 | + return; |
323 | + } |
324 | + if (shout_set_description(m_pShout, baStreamDesc.data()) != SHOUTERR_SUCCESS) { |
325 | + errorDialog("Error setting stream description!", shout_get_error(m_pShout)); |
326 | + return; |
327 | + } |
328 | + if (shout_set_genre(m_pShout, baStreamGenre.data()) != SHOUTERR_SUCCESS) { |
329 | + errorDialog("Error setting stream genre!", shout_get_error(m_pShout)); |
330 | + return; |
331 | + } |
332 | + if (shout_set_url(m_pShout, baStreamWebsite.data()) != SHOUTERR_SUCCESS) { |
333 | errorDialog("Error setting stream url!", shout_get_error(m_pShout)); |
334 | return; |
335 | } |
336 | @@ -251,24 +239,24 @@ |
337 | } else if ( ! qstricmp(baServerType.data(), "Icecast 1")) { |
338 | protocol = SHOUT_PROTOCOL_XAUDIOCAST; |
339 | } else { |
340 | - errorDialog("Error: unknown server protocol!", shout_get_error(m_pShout)); |
341 | + errorDialog("Error: unknown server protocol!", shout_get_error(m_pShout)); |
342 | return; |
343 | } |
344 | |
345 | if (( protocol == SHOUT_PROTOCOL_ICY ) && ( format != SHOUT_FORMAT_MP3)) { |
346 | - errorDialog("Error: libshout only supports Shoutcast with MP3 format!", shout_get_error(m_pShout)); |
347 | - return; |
348 | + errorDialog("Error: libshout only supports Shoutcast with MP3 format!", shout_get_error(m_pShout)); |
349 | + return; |
350 | } |
351 | |
352 | if (shout_set_protocol(m_pShout, protocol) != SHOUTERR_SUCCESS) { |
353 | - errorDialog("Error setting protocol!", shout_get_error(m_pShout)); |
354 | + errorDialog("Error setting protocol!", shout_get_error(m_pShout)); |
355 | return; |
356 | } |
357 | |
358 | // Initialize m_encoder |
359 | - if(m_encoder) { |
360 | - delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate |
361 | - } |
362 | + if(m_encoder) { |
363 | + delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate |
364 | + } |
365 | if ( ! qstrcmp(m_baFormat, "MP3")) { |
366 | m_encoder = new EncoderMp3(this); |
367 | |
368 | @@ -281,11 +269,11 @@ |
369 | return; |
370 | } |
371 | if (m_encoder->initEncoder(baBitrate.toInt()) < 0) { |
372 | - //e.g., if lame is not found |
373 | - //init m_encoder itself will display a message box |
374 | - qDebug() << "**** Encoder init failed"; |
375 | - delete m_encoder; |
376 | - m_encoder = NULL; |
377 | + //e.g., if lame is not found |
378 | + //init m_encoder itself will display a message box |
379 | + qDebug() << "**** Encoder init failed"; |
380 | + delete m_encoder; |
381 | + m_encoder = NULL; |
382 | } |
383 | |
384 | } |
385 | @@ -304,19 +292,19 @@ |
386 | m_iShoutFailures = 0; |
387 | // set to a high number to automatically update the metadata |
388 | // on the first change |
389 | - m_pMetaDataLife = 31337; |
390 | - //If static metadata is available, we only need to send metadata one time |
391 | - m_firstCall = false; |
392 | + m_iMetaDataLife = 31337; |
393 | + //If static metadata is available, we only need to send metadata one time |
394 | + m_firstCall = false; |
395 | |
396 | - /*Check if m_encoder is initalized |
397 | - * Encoder is initalized in updateFromPreferences which is called always before serverConnect() |
398 | - * If m_encoder is NULL, then we propably want to use MP3 streaming, however, lame could not be found |
399 | - * It does not make sense to connect |
400 | - */ |
401 | - if(m_encoder == NULL){ |
402 | - m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); |
403 | - return false; |
404 | - } |
405 | + /*Check if m_encoder is initalized |
406 | + * Encoder is initalized in updateFromPreferences which is called always before serverConnect() |
407 | + * If m_encoder is NULL, then we propably want to use MP3 streaming, however, lame could not be found |
408 | + * It does not make sense to connect |
409 | + */ |
410 | + if(m_encoder == NULL){ |
411 | + m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); |
412 | + return false; |
413 | + } |
414 | const int iMaxTries = 3; |
415 | while (!m_bQuit && m_iShoutFailures < iMaxTries) { |
416 | if (m_pShout) |
417 | @@ -338,8 +326,8 @@ |
418 | if (m_iShoutFailures == iMaxTries) { |
419 | if (m_pShout) |
420 | shout_close(m_pShout); |
421 | - m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); |
422 | - |
423 | + m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); |
424 | + |
425 | } |
426 | if (m_bQuit) { |
427 | if (m_pShout) |
428 | @@ -348,23 +336,23 @@ |
429 | } |
430 | |
431 | m_iShoutFailures = 0; |
432 | - int timeout = 0; |
433 | + int timeout = 0; |
434 | while (m_iShoutStatus == SHOUTERR_BUSY && timeout < TIMEOUT) { |
435 | qDebug() << "Connection pending. Sleeping..."; |
436 | sleep(1); |
437 | m_iShoutStatus = shout_get_connected(m_pShout); |
438 | - ++ timeout; |
439 | + ++ timeout; |
440 | } |
441 | if (m_iShoutStatus == SHOUTERR_CONNECTED) { |
442 | qDebug() << "***********Connected to Shoutcast server..."; |
443 | return true; |
444 | } |
445 | - //otherwise disable shoutcast in preferences |
446 | - m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); |
447 | - if(m_pShout){ |
448 | - shout_close(m_pShout); |
449 | - //errorDialog(tr("Mixxx could not connect to the server"), tr("Please check your connection to the Internet and verify that your username and password are correct.")); |
450 | - } |
451 | + //otherwise disable shoutcast in preferences |
452 | + m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0")); |
453 | + if(m_pShout){ |
454 | + shout_close(m_pShout); |
455 | + //errorDialog(tr("Mixxx could not connect to the server"), tr("Please check your connection to the Internet and verify that your username and password are correct.")); |
456 | + } |
457 | |
458 | return false; |
459 | } |
460 | @@ -431,111 +419,60 @@ |
461 | void EngineShoutcast::process(const CSAMPLE *, const CSAMPLE *pOut, const int iBufferSize) |
462 | { |
463 | QMutexLocker locker(&m_shoutMutex); |
464 | - //Check to see if Shoutcast is enabled, and pass the samples off to be broadcast if necessary. |
465 | + //Check to see if Shoutcast is enabled, and pass the samples off to be broadcast if necessary. |
466 | bool prefEnabled = (m_pConfig->getValueString(ConfigKey("[Shoutcast]","enabled")).toInt() == 1); |
467 | |
468 | if (prefEnabled) { |
469 | - if(!isConnected()){ |
470 | - //Initialize the m_pShout structure with the info from Mixxx's m_shoutcast preferences. |
471 | - updateFromPreferences(); |
472 | + if(!isConnected()){ |
473 | + //Initialize the m_pShout structure with the info from Mixxx's m_shoutcast preferences. |
474 | + updateFromPreferences(); |
475 | |
476 | - if(serverConnect()){ |
477 | - ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); |
478 | - props->setType(DLG_INFO); |
479 | - props->setTitle(tr("Live broadcasting")); |
480 | - props->setText(tr("Mixxx has successfully connected to the shoutcast server")); |
481 | - ErrorDialogHandler::instance()->requestErrorDialog(props); |
482 | - } |
483 | + if(serverConnect()){ |
484 | + ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); |
485 | + props->setType(DLG_INFO); |
486 | + props->setTitle(tr("Live broadcasting")); |
487 | + props->setText(tr("Mixxx has successfully connected to the shoutcast server")); |
488 | + ErrorDialogHandler::instance()->requestErrorDialog(props); |
489 | + } |
490 | else{ |
491 | errorDialog(tr("Mixxx could not connect to streaming server"), tr("Please check your connection to the Internet and verify that your username and password are correct.")); |
492 | |
493 | } |
494 | - } |
495 | + } |
496 | //send to shoutcast, if connection has been established |
497 | if (m_iShoutStatus != SHOUTERR_CONNECTED) |
498 | return; |
499 | |
500 | if (iBufferSize > 0 && m_encoder){ |
501 | m_encoder->encodeBuffer(pOut, iBufferSize); //encode and send to shoutcast |
502 | - } |
503 | + } |
504 | //Check if track has changed and submit its new metadata to shoutcast |
505 | if (metaDataHasChanged()) |
506 | updateMetaData(); |
507 | |
508 | if (m_pUpdateShoutcastFromPrefs->get() > 0.0f){ |
509 | - /* |
510 | - * You cannot change bitrate, hostname, etc while connected to a stream |
511 | - */ |
512 | - serverDisconnect(); |
513 | - updateFromPreferences(); |
514 | - serverConnect(); |
515 | - } |
516 | + /* |
517 | + * You cannot change bitrate, hostname, etc while connected to a stream |
518 | + */ |
519 | + serverDisconnect(); |
520 | + updateFromPreferences(); |
521 | + serverConnect(); |
522 | + } |
523 | } |
524 | //if shoutcast is disabled |
525 | - else{ |
526 | - if(isConnected()){ |
527 | - serverDisconnect(); |
528 | - ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); |
529 | - props->setType(DLG_INFO); |
530 | - props->setTitle(tr("Live broadcasting")); |
531 | - props->setText(tr("Mixxx has successfully disconnected to the shoutcast server")); |
532 | - |
533 | - ErrorDialogHandler::instance()->requestErrorDialog(props); |
534 | - } |
535 | - } |
536 | -} |
537 | - |
538 | -/* |
539 | - * Algorithm which simply flips the lowest and/or second lowest bits, |
540 | - * bits 1 and 2, to represent which track is active and returns the result. |
541 | - */ |
542 | -int EngineShoutcast::getActiveTracks() |
543 | -{ |
544 | - int tracks = 0; |
545 | - |
546 | - |
547 | - if (ControlObject::getControl(ConfigKey("[Channel1]","play"))->get()==1.) tracks |= 1; |
548 | - if (ControlObject::getControl(ConfigKey("[Channel2]","play"))->get()==1.) tracks |= 2; |
549 | - |
550 | - if (tracks == 0) |
551 | - return 0; |
552 | - |
553 | - // Detect the dominant track by checking the crossfader and volume levels |
554 | - if ((tracks & 1) && (tracks & 2)) { |
555 | - |
556 | - if ((m_pVolume1->get() == 0) && (m_pVolume2->get() == 0)) |
557 | - return 0; |
558 | - |
559 | - if (m_pVolume2->get() == 0) { |
560 | - tracks = 1; |
561 | - } |
562 | - else if ( m_pVolume1->get() == 0) { |
563 | - tracks = 2; |
564 | - } |
565 | - // allow a bit of leeway with the crossfader |
566 | - else if ((m_pCrossfader->get() < 0.05) && (m_pCrossfader->get() > -0.05)) { |
567 | - |
568 | - if (m_pVolume1->get() > m_pVolume2->get()) { |
569 | - tracks = 1; |
570 | - } |
571 | - else if (m_pVolume1->get() < m_pVolume2->get()) { |
572 | - tracks = 2; |
573 | - } |
574 | - |
575 | - } |
576 | - else if ( m_pCrossfader->get() < -0.05 ) { |
577 | - tracks = 1; |
578 | - } |
579 | - else if ( m_pCrossfader->get() > 0.05 ) { |
580 | - tracks = 2; |
581 | - } |
582 | - |
583 | + else{ |
584 | + if(isConnected()){ |
585 | + serverDisconnect(); |
586 | + ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); |
587 | + props->setType(DLG_INFO); |
588 | + props->setTitle(tr("Live broadcasting")); |
589 | + props->setText(tr("Mixxx has successfully disconnected to the shoutcast server")); |
590 | + |
591 | + ErrorDialogHandler::instance()->requestErrorDialog(props); |
592 | + } |
593 | } |
594 | - |
595 | - return tracks; |
596 | } |
597 | |
598 | - |
599 | /* |
600 | * Check if the metadata has changed since the previous check. |
601 | * We also check when was the last check performed to avoid using |
602 | @@ -545,50 +482,35 @@ |
603 | bool EngineShoutcast::metaDataHasChanged() |
604 | { |
605 | QMutexLocker locker(&m_shoutMutex); |
606 | - int tracks; |
607 | - TrackPointer newMetaData; |
608 | - bool changed = false; |
609 | - |
610 | - |
611 | - if ( m_pMetaDataLife < 16 ) { |
612 | - m_pMetaDataLife++; |
613 | - return false; |
614 | - } |
615 | - |
616 | - m_pMetaDataLife = 0; |
617 | - |
618 | - |
619 | - tracks = getActiveTracks(); |
620 | - |
621 | - switch (tracks) |
622 | - { |
623 | - case 0: |
624 | - // no tracks are playing |
625 | - // we should set the metadata to nothing |
626 | - break; |
627 | - case 1: |
628 | - // track 1 is active |
629 | - newMetaData = PlayerInfo::Instance().getTrackInfo("[Channel1]"); |
630 | - if (newMetaData != m_pMetaData) |
631 | - { |
632 | - m_pMetaData = newMetaData; |
633 | - changed = true; |
634 | - } |
635 | - break; |
636 | - case 2: |
637 | - // track 2 is active |
638 | - newMetaData = PlayerInfo::Instance().getTrackInfo("[Channel2]"); |
639 | - if (newMetaData != m_pMetaData) |
640 | - { |
641 | - m_pMetaData = newMetaData; |
642 | - changed = true; |
643 | - } |
644 | - break; |
645 | - case 3: |
646 | - // both tracks are active, just stick with it for now |
647 | - break; |
648 | - } |
649 | - return changed; |
650 | + TrackPointer pTrack; |
651 | + |
652 | + |
653 | + if ( m_iMetaDataLife < 16 ) { |
654 | + m_iMetaDataLife++; |
655 | + return false; |
656 | + } |
657 | + |
658 | + m_iMetaDataLife = 0; |
659 | + |
660 | + |
661 | + pTrack = PlayerInfo::Instance().getCurrentPlayingTrack(); |
662 | + if ( !pTrack ) |
663 | + return false; |
664 | + |
665 | + if ( m_pMetaData ) { |
666 | + if ((pTrack->getId() == -1) || (m_pMetaData->getId() == -1)) { |
667 | + if ((pTrack->getArtist() == m_pMetaData->getArtist()) && |
668 | + (pTrack->getTitle() == m_pMetaData->getArtist())) { |
669 | + return false; |
670 | + } |
671 | + } |
672 | + else if (pTrack->getId() == m_pMetaData->getId()) { |
673 | + return false; |
674 | + } |
675 | + } |
676 | + |
677 | + m_pMetaData = pTrack; |
678 | + return true; |
679 | } |
680 | |
681 | /* |
682 | @@ -603,51 +525,48 @@ |
683 | return; |
684 | |
685 | QByteArray baSong = ""; |
686 | - /** |
687 | + /** |
688 | * If track has changed and static metadata is disabled |
689 | - * Send new metadata to shoutcast! |
690 | + * Send new metadata to shoutcast! |
691 | * This works only for MP3 streams properly as stated in comments, see shout.h |
692 | - * WARNING: Changing OGG metadata dynamically by using shout_set_metadata |
693 | - * will cause stream interruptions to listeners |
694 | - * |
695 | - * Also note: Do not try to include Vorbis comments in OGG packages and send them to stream. |
696 | - * This was done in EncoderVorbis previously and caused interruptions on track change as well |
697 | - * which sounds awful to listeners. |
698 | - |
699 | - * To conlcude: Only write OGG metadata one time, i.e., if static metadata is used. |
700 | - */ |
701 | + * WARNING: Changing OGG metadata dynamically by using shout_set_metadata |
702 | + * will cause stream interruptions to listeners |
703 | + * |
704 | + * Also note: Do not try to include Vorbis comments in OGG packages and send them to stream. |
705 | + * This was done in EncoderVorbis previously and caused interruptions on track change as well |
706 | + * which sounds awful to listeners. |
707 | + * To conlcude: Only write OGG metadata one time, i.e., if static metadata is used. |
708 | + */ |
709 | |
710 | |
711 | //If we use MP3 streaming and want dynamic metadata changes |
712 | - if(!m_custom_metadata && !qstrcmp(m_baFormat, "MP3")){ |
713 | - if (m_pMetaData != NULL) { |
714 | - // convert QStrings to char*s |
715 | - QByteArray baArtist = m_pMetaData->getArtist().toLatin1(); |
716 | - QByteArray baTitle = m_pMetaData->getTitle().toLatin1(); |
717 | - |
718 | - if (baArtist.isEmpty()) |
719 | - baSong = baTitle; |
720 | - else |
721 | - baSong = baArtist + " - " + baTitle; |
722 | + if(!m_custom_metadata && !qstrcmp(m_baFormat, "MP3")){ |
723 | + if (m_pMetaData != NULL) { |
724 | + // convert QStrings to char*s |
725 | + QByteArray baArtist = m_pMetaData->getArtist().toLatin1(); |
726 | + QByteArray baTitle = m_pMetaData->getTitle().toLatin1(); |
727 | + |
728 | + if (baArtist.isEmpty()) |
729 | + baSong = baTitle; |
730 | + else |
731 | + baSong = baArtist + " - " + baTitle; |
732 | |
733 | - /** Update metadata */ |
734 | - shout_metadata_add(m_pShoutMetaData, "song", baSong.data()); |
735 | - shout_set_metadata(m_pShout, m_pShoutMetaData); |
736 | - } |
737 | - } |
738 | + /** Update metadata */ |
739 | + shout_metadata_add(m_pShoutMetaData, "song", baSong.data()); |
740 | + shout_set_metadata(m_pShout, m_pShoutMetaData); |
741 | + } |
742 | + } |
743 | //Otherwise we might use static metadata |
744 | - else{ |
745 | - /** If we use static metadata, we only need to call the following line once **/ |
746 | - if(m_custom_metadata && !m_firstCall){ |
747 | - baSong = m_baCustom_artist + " - " + m_baCustom_title; |
748 | - /** Update metadata */ |
749 | - shout_metadata_add(m_pShoutMetaData, "song", baSong.data()); |
750 | - shout_set_metadata(m_pShout, m_pShoutMetaData); |
751 | - m_firstCall = true; |
752 | - } |
753 | - } |
754 | - |
755 | - |
756 | + else{ |
757 | + /** If we use static metadata, we only need to call the following line once **/ |
758 | + if(m_custom_metadata && !m_firstCall){ |
759 | + baSong = m_baCustom_artist + " - " + m_baCustom_title; |
760 | + /** Update metadata */ |
761 | + shout_metadata_add(m_pShoutMetaData, "song", baSong.data()); |
762 | + shout_set_metadata(m_pShout, m_pShoutMetaData); |
763 | + m_firstCall = true; |
764 | + } |
765 | + } |
766 | } |
767 | /* -------- ------------------------------------------------------ |
768 | Purpose: Common error dialog creation code for run-time exceptions |
769 | |
770 | === modified file 'mixxx/src/engine/engineshoutcast.h' |
771 | --- mixxx/src/engine/engineshoutcast.h 2010-09-07 07:50:15 +0000 |
772 | +++ mixxx/src/engine/engineshoutcast.h 2011-03-25 05:39:41 +0000 |
773 | @@ -66,16 +66,13 @@ |
774 | TrackPointer m_pMetaData; |
775 | shout_t *m_pShout; |
776 | shout_metadata_t *m_pShoutMetaData; |
777 | - int m_pMetaDataLife; |
778 | + int m_iMetaDataLife; |
779 | long m_iShoutStatus; |
780 | long m_iShoutFailures; |
781 | ConfigObject<ConfigValue> *m_pConfig; |
782 | Encoder *m_encoder; |
783 | ControlObject* m_pShoutcastNeedUpdateFromPrefs; |
784 | ControlObjectThreadMain* m_pUpdateShoutcastFromPrefs; |
785 | - ControlObjectThread* m_pCrossfader; |
786 | - ControlObjectThread* m_pVolume1; |
787 | - ControlObjectThread* m_pVolume2; |
788 | volatile bool m_bQuit; |
789 | QMutex m_shoutMutex; |
790 | /** static metadata according to prefereneces **/ |
791 | |
792 | === modified file 'mixxx/src/playerinfo.cpp' |
793 | --- mixxx/src/playerinfo.cpp 2010-10-24 09:50:11 +0000 |
794 | +++ mixxx/src/playerinfo.cpp 2011-03-25 05:39:41 +0000 |
795 | @@ -17,6 +17,45 @@ |
796 | #include <QMutexLocker> |
797 | |
798 | #include "playerinfo.h" |
799 | +#include "controlobject.h" |
800 | +#include "controlobjectthread.h" |
801 | +#include "engine/enginexfader.h" |
802 | + |
803 | +PlayerInfo::PlayerInfo() |
804 | +{ |
805 | + int i; |
806 | + m_iNumDecks = ControlObject::getControl(ConfigKey("[Master]","num_decks"))->get(); |
807 | + |
808 | + for (i = 1; i <= m_iNumDecks; i++) { |
809 | + QString chan = QString("[Channel%1]").arg(i); |
810 | + |
811 | + m_listCOPlay[chan] = new ControlObjectThread(ControlObject::getControl(ConfigKey(chan, "play"))); |
812 | + m_listCOVolume[chan] = new ControlObjectThread(ControlObject::getControl(ConfigKey(chan, "volume"))); |
813 | + m_listCOOrientation[chan] = new ControlObjectThread(ControlObject::getControl(ConfigKey(chan, "orientation"))); |
814 | + m_listCOpregain[chan] = new ControlObjectThread(ControlObject::getControl(ConfigKey(chan, "pregain"))); |
815 | + } |
816 | + |
817 | + m_COxfader = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Master]","crossfader"))); |
818 | +} |
819 | + |
820 | +PlayerInfo::~PlayerInfo() |
821 | +{ |
822 | + int i; |
823 | + |
824 | + |
825 | + m_loadedTrackMap.clear(); |
826 | + |
827 | + for (i = 1; i <= m_iNumDecks; i++) { |
828 | + QString chan = QString("[Channel%1]").arg(i); |
829 | + |
830 | + delete m_listCOPlay[chan]; |
831 | + delete m_listCOVolume[chan]; |
832 | + delete m_listCOOrientation[chan]; |
833 | + delete m_listCOpregain[chan]; |
834 | + } |
835 | + |
836 | + delete m_COxfader; |
837 | +} |
838 | |
839 | PlayerInfo &PlayerInfo::Instance() |
840 | { |
841 | @@ -24,14 +63,6 @@ |
842 | return playerInfo; |
843 | } |
844 | |
845 | -PlayerInfo::PlayerInfo() { |
846 | -} |
847 | - |
848 | -PlayerInfo::~PlayerInfo() |
849 | -{ |
850 | - m_loadedTrackMap.clear(); |
851 | -} |
852 | - |
853 | TrackPointer PlayerInfo::getTrackInfo(QString group) |
854 | { |
855 | QMutexLocker locker(&m_mutex); |
856 | @@ -48,3 +79,61 @@ |
857 | QMutexLocker locker(&m_mutex); |
858 | m_loadedTrackMap[group] = track; |
859 | } |
860 | + |
861 | +int PlayerInfo::getCurrentPlayingDeck() |
862 | +{ |
863 | + QMutexLocker locker(&m_mutex); |
864 | + int MaxVolume = 0; |
865 | + int MaxDeck = 0; |
866 | + int i; |
867 | + |
868 | + |
869 | + for (i = 1; i <= m_iNumDecks; i++) { |
870 | + QString chan = QString("[Channel%1]").arg(i); |
871 | + float fvol; |
872 | + float xfl, xfr, xfvol; |
873 | + float dvol; |
874 | + int orient; |
875 | + |
876 | + if ( m_listCOPlay[chan]->get() == 0.0 ) |
877 | + continue; |
878 | + |
879 | + if ( m_listCOpregain[chan]->get() <= 0.5 ) |
880 | + continue; |
881 | + |
882 | + if ((fvol = m_listCOVolume[chan]->get()) == 0.0 ) |
883 | + continue; |
884 | + |
885 | + EngineXfader::getXfadeGains(xfl, xfr, m_COxfader->get(), 1.0, 0.0); |
886 | + |
887 | + // Orientation goes: left is 0, center is 1, right is 2. |
888 | + // Leave math out of it... |
889 | + orient = m_listCOOrientation[chan]->get(); |
890 | + if ( orient == 0 ) |
891 | + xfvol = xfl; |
892 | + else if ( orient == 2 ) |
893 | + xfvol = xfr; |
894 | + else |
895 | + xfvol = 1; |
896 | + |
897 | + dvol = fvol * xfvol; |
898 | + if ( dvol > MaxVolume ) { |
899 | + MaxDeck = i; |
900 | + MaxVolume = dvol; |
901 | + } |
902 | + } |
903 | + |
904 | + return MaxDeck; |
905 | +} |
906 | + |
907 | +TrackPointer PlayerInfo::getCurrentPlayingTrack() |
908 | +{ |
909 | + int deck = getCurrentPlayingDeck(); |
910 | + if ( deck ) { |
911 | + QString chan = QString("[Channel%1]").arg(deck); |
912 | + return getTrackInfo(chan); |
913 | + } |
914 | + |
915 | + return TrackPointer(); |
916 | +} |
917 | + |
918 | |
919 | === modified file 'mixxx/src/playerinfo.h' |
920 | --- mixxx/src/playerinfo.h 2010-10-24 09:50:11 +0000 |
921 | +++ mixxx/src/playerinfo.h 2011-03-25 05:39:41 +0000 |
922 | @@ -21,6 +21,9 @@ |
923 | #include <QMutex> |
924 | #include <QMap> |
925 | |
926 | + |
927 | +class ControlObjectThread; |
928 | + |
929 | #include "trackinfoobject.h" |
930 | |
931 | class PlayerInfo : public QObject |
932 | @@ -30,13 +33,21 @@ |
933 | static PlayerInfo &Instance(); |
934 | TrackPointer getTrackInfo(QString group); |
935 | void setTrackInfo(QString group, TrackPointer trackInfoObj); |
936 | + int getCurrentPlayingDeck(); |
937 | + TrackPointer getCurrentPlayingTrack(); |
938 | private: |
939 | PlayerInfo(); |
940 | ~PlayerInfo(); |
941 | PlayerInfo(PlayerInfo const&); |
942 | PlayerInfo &operator= (PlayerInfo const&); |
943 | QMutex m_mutex; |
944 | + int m_iNumDecks; |
945 | + ControlObjectThread* m_COxfader; |
946 | QMap<QString, TrackPointer> m_loadedTrackMap; |
947 | + QMap<QString, ControlObjectThread*> m_listCOPlay; |
948 | + QMap<QString, ControlObjectThread*> m_listCOVolume; |
949 | + QMap<QString, ControlObjectThread*> m_listCOOrientation; |
950 | + QMap<QString, ControlObjectThread*> m_listCOpregain; |
951 | }; |
952 | |
953 | #endif |
954 | |
955 | === modified file 'mixxx/src/recording/enginerecord.cpp' |
956 | --- mixxx/src/recording/enginerecord.cpp 2010-11-01 17:24:48 +0000 |
957 | +++ mixxx/src/recording/enginerecord.cpp 2011-03-25 05:39:41 +0000 |
958 | @@ -21,6 +21,7 @@ |
959 | #include "configobject.h" |
960 | #include "controlobjectthread.h" |
961 | #include "controlobject.h" |
962 | +#include "trackinfoobject.h" |
963 | #include "dlgprefrecord.h" |
964 | #ifdef __SHOUTCAST__ |
965 | #include "encodervorbis.h" |
966 | @@ -47,10 +48,12 @@ |
967 | m_recReady = new ControlObjectThread(m_recReadyCO); |
968 | m_samplerate = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Master]", "samplerate"))); |
969 | |
970 | + m_iMetaDataLife = 0; |
971 | } |
972 | |
973 | EngineRecord::~EngineRecord() |
974 | { |
975 | + closeCueFile(); |
976 | closeFile(); |
977 | if(m_recReadyCO) delete m_recReadyCO; |
978 | if(m_recReady) delete m_recReady; |
979 | @@ -67,6 +70,8 @@ |
980 | m_baTitle = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Title")).toLatin1(); |
981 | m_baAuthor = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Author")).toLatin1(); |
982 | m_baAlbum = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Album")).toLatin1(); |
983 | + m_cuefilename = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "CuePath")).toLatin1(); |
984 | + m_bCueIsEnabled = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "CueEnabled")).toInt(); |
985 | |
986 | if(m_encoder){ |
987 | delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate |
988 | @@ -112,8 +117,48 @@ |
989 | |
990 | } |
991 | |
992 | +/* |
993 | + * Check if the metadata has changed since the previous check. |
994 | + * We also check when was the last check performed to avoid using |
995 | + * too much CPU and as well to avoid changing the metadata during |
996 | + * scratches. |
997 | + */ |
998 | +bool EngineRecord::metaDataHasChanged() |
999 | +{ |
1000 | + TrackPointer pTrack; |
1001 | + |
1002 | + |
1003 | + if ( m_iMetaDataLife < 16 ) { |
1004 | + m_iMetaDataLife++; |
1005 | + return false; |
1006 | + } |
1007 | + m_iMetaDataLife = 0; |
1008 | + |
1009 | + pTrack = PlayerInfo::Instance().getCurrentPlayingTrack(); |
1010 | + if ( !pTrack ) |
1011 | + return false; |
1012 | + |
1013 | + if ( m_pCurrentTrack ) { |
1014 | + if ((pTrack->getId() == -1) || (m_pCurrentTrack->getId() == -1)) { |
1015 | + if ((pTrack->getArtist() == m_pCurrentTrack->getArtist()) && |
1016 | + (pTrack->getTitle() == m_pCurrentTrack->getArtist())) { |
1017 | + return false; |
1018 | + } |
1019 | + } |
1020 | + else if (pTrack->getId() == m_pCurrentTrack->getId()) { |
1021 | + return false; |
1022 | + } |
1023 | + } |
1024 | + |
1025 | + m_pCurrentTrack = pTrack; |
1026 | + return true; |
1027 | +} |
1028 | + |
1029 | void EngineRecord::process(const CSAMPLE * pIn, const CSAMPLE * pOut, const int iBufferSize) |
1030 | { |
1031 | + // Calculate the latency of this buffer |
1032 | + m_dLatency = (double)iBufferSize / m_samplerate->get(); |
1033 | + |
1034 | //if recording is disabled |
1035 | if(m_recReady->get() == RECORD_OFF){ |
1036 | if(fileOpen()){ |
1037 | @@ -126,6 +171,12 @@ |
1038 | if(openFile()){ |
1039 | qDebug("Setting record flag to: ON"); |
1040 | m_recReady->slotSet(RECORD_ON); |
1041 | + |
1042 | + if ( m_bCueIsEnabled ) { |
1043 | + openCueFile(); |
1044 | + m_cuesamplepos = 0; |
1045 | + m_cuetrack = 0; |
1046 | + } |
1047 | } |
1048 | else{ //Maybe the encoder could not be initialized |
1049 | qDebug("Setting record flag to: OFF"); |
1050 | @@ -139,14 +190,65 @@ |
1051 | sf_write_float(m_sndfile, pIn, iBufferSize); |
1052 | } |
1053 | else{ |
1054 | - if(!m_encoder) return; |
1055 | - //Compress audio. Encoder will call method 'write()' below to write a file stream |
1056 | - m_encoder->encodeBuffer(pIn, iBufferSize); |
1057 | - } |
1058 | + if ( m_encoder) { |
1059 | + //Compress audio. Encoder will call method 'write()' below to write a file stream |
1060 | + m_encoder->encodeBuffer(pIn, iBufferSize); |
1061 | + } |
1062 | + } |
1063 | + |
1064 | + if ( m_bCueIsEnabled ) { |
1065 | + |
1066 | + if ( metaDataHasChanged()) { |
1067 | + m_cuetrack++; |
1068 | + writeCueLine(); |
1069 | + m_cuefile.flush(); |
1070 | + } |
1071 | + |
1072 | + m_cuesamplepos += iBufferSize; |
1073 | + |
1074 | + } |
1075 | + |
1076 | } |
1077 | |
1078 | - |
1079 | -} |
1080 | +} |
1081 | + |
1082 | +void EngineRecord::writeCueLine() |
1083 | +{ |
1084 | + // account for multiple channels |
1085 | + unsigned long samplerate = m_samplerate->get() * 2; |
1086 | + // CDDA is specified as having 75 frames a second |
1087 | + unsigned long frames = ((unsigned long) |
1088 | + ((m_cuesamplepos / (samplerate / 75))) |
1089 | + % 75); |
1090 | + |
1091 | + unsigned long seconds = ((unsigned long) |
1092 | + (m_cuesamplepos / samplerate) |
1093 | + % 60 ); |
1094 | + |
1095 | + unsigned long minutes = m_cuesamplepos / (samplerate * 60); |
1096 | + |
1097 | + m_cuefile.write(QString(" TRACK %1 AUDIO\n") |
1098 | + .arg((double)m_cuetrack, 2, 'f', 0, '0') |
1099 | + .toLatin1() |
1100 | + ); |
1101 | + |
1102 | + m_cuefile.write(QString(" TITLE %1\n") |
1103 | + .arg(m_pCurrentTrack->getTitle()).toLatin1()); |
1104 | + m_cuefile.write(QString(" PERFORMER %1\n") |
1105 | + .arg(m_pCurrentTrack->getArtist()).toLatin1()); |
1106 | + |
1107 | + // Woefully inaccurate (at the seconds level anyways). |
1108 | + // We'd need a signal fired state tracker |
1109 | + // for the track detection code. |
1110 | + m_cuefile.write(QString(" INDEX 01 %1:%2:%3\n") |
1111 | + .arg((double)minutes, 2, 'f', 0, '0') |
1112 | + .arg((double)seconds, 2, 'f', 0, '0') |
1113 | + .arg((double)frames, 2, 'f', 0, '0') |
1114 | + .toLatin1() |
1115 | + ); |
1116 | + |
1117 | +} |
1118 | + |
1119 | /** encoder will call this method to write compressed audio **/ |
1120 | void EngineRecord::write(unsigned char *header, unsigned char *body, |
1121 | int headerLen, int bodyLen) |
1122 | @@ -232,9 +334,44 @@ |
1123 | ErrorDialogHandler::instance()->requestErrorDialog(props); |
1124 | return false; |
1125 | } |
1126 | - return true; |
1127 | - |
1128 | -} |
1129 | + |
1130 | + return true; |
1131 | +} |
1132 | + |
1133 | +bool EngineRecord::openCueFile() { |
1134 | + if ( m_cuefilename.length() <= 0 ) |
1135 | + { |
1136 | + return false; |
1137 | + } |
1138 | + |
1139 | + qDebug() << "Opening Cue File:" << m_cuefilename; |
1140 | + m_cuefile.setFileName(m_cuefilename); |
1141 | + m_cuefile.open(QIODevice::WriteOnly); |
1142 | + |
1143 | + if ( m_baAuthor.length() > 0 ) { |
1144 | + m_cuefile.write(QString("PERFORMER \"%1\"\n") |
1145 | + .arg(QString(m_baAuthor).replace(QString("\""), QString("\\\""))) |
1146 | + .toLatin1() |
1147 | + ); |
1148 | + } |
1149 | + |
1150 | + if ( m_baTitle.length() > 0 ) { |
1151 | + m_cuefile.write(QString("TITLE \"%1\"\n") |
1152 | + .arg(QString(m_baTitle).replace(QString("\""), QString("\\\""))) |
1153 | + .toLatin1() |
1154 | + ); |
1155 | + } |
1156 | + |
1157 | + m_cuefile.write(QString("FILE \"%1\" %2%3\n") |
1158 | + .arg(QString(m_filename).replace(QString("\""), QString("\\\""))) |
1159 | + .arg(QString(m_Encoding).toUpper()) |
1160 | + .arg((m_Encoding == ENCODING_WAVE ? 'E' : ' ')) |
1161 | + .toLatin1() |
1162 | + ); |
1163 | + |
1164 | + return true; |
1165 | +} |
1166 | + |
1167 | void EngineRecord::closeFile(){ |
1168 | if(m_Encoding == ENCODING_WAVE || m_Encoding == ENCODING_AIFF){ |
1169 | if(m_sndfile != NULL){ |
1170 | @@ -255,3 +392,8 @@ |
1171 | } |
1172 | } |
1173 | |
1174 | +void EngineRecord::closeCueFile() { |
1175 | + if ( m_cuefile.handle() != -1) { |
1176 | + m_cuefile.close(); |
1177 | + } |
1178 | +} |
1179 | |
1180 | === modified file 'mixxx/src/recording/enginerecord.h' |
1181 | --- mixxx/src/recording/enginerecord.h 2010-11-01 17:24:48 +0000 |
1182 | +++ mixxx/src/recording/enginerecord.h 2011-03-25 05:39:41 +0000 |
1183 | @@ -29,6 +29,8 @@ |
1184 | #include "encoder.h" |
1185 | #include "errordialoghandler.h" |
1186 | |
1187 | +#include "trackinfoobject.h" |
1188 | + |
1189 | #define THRESHOLD_REC 2. //high enough that its not triggered by white noise |
1190 | |
1191 | class ControlLogpotmeter; |
1192 | @@ -49,8 +51,14 @@ |
1193 | void closeFile(); |
1194 | void updateFromPreferences(); |
1195 | bool fileOpen(); |
1196 | + bool openCueFile(); |
1197 | + void closeCueFile(); |
1198 | |
1199 | private: |
1200 | + int getActiveTracks(); |
1201 | + bool metaDataHasChanged(); |
1202 | + void writeCueLine(); |
1203 | + |
1204 | ConfigObject<ConfigValue> *m_config; |
1205 | Encoder *m_encoder; |
1206 | QByteArray m_OGGquality; |
1207 | @@ -62,6 +70,7 @@ |
1208 | QByteArray m_baAlbum; |
1209 | |
1210 | QFile m_file; |
1211 | + QFile m_cuefile; |
1212 | QDataStream m_datastream; |
1213 | SNDFILE *m_sndfile; |
1214 | SF_INFO m_sfInfo; |
1215 | @@ -70,6 +79,16 @@ |
1216 | ControlObject* m_recReadyCO; |
1217 | |
1218 | ControlObjectThread* m_samplerate; |
1219 | + |
1220 | + int m_iMetaDataLife; |
1221 | + TrackPointer m_pCurrentTrack; |
1222 | + int m_iNumChannels; |
1223 | + double m_dLatency; |
1224 | + |
1225 | + QByteArray m_cuefilename; |
1226 | + unsigned long m_cuesamplepos; |
1227 | + unsigned long m_cuetrack; |
1228 | + bool m_bCueIsEnabled; |
1229 | }; |
1230 | |
1231 | #endif |
Hey Phil,
Really sorry for taking so long to get to this! This all looks nice, I just have some general issues with lack of n-deck support and the duplication between EngineShoutcast and EngineRecord:
- The metadata recording is hard coded to work with only 2 decks. I'd really rather not add more 2-deck code if we don't have to. Can you use the [Master],num_decks control (alternatively, thread the PlayerManager into EngineRecord)? Every deck has an "orientation" control which indicates the side of the crossfader it is attached to. You can use this in conjunction with its volume/pregain faders to figure out if they are "hearable". PlayerInfo is N-Deck ready, so that shouldn't be a problem.
- Assuming you'll change this given the above comment anyway, but ControlObject: :getControl shouldn't be used in code that gets called on a schedule. As written it'll be doing 5 locks of the global ControlObject static mutex per call (plus 5 hash lookups) to getActiveTracks() when it could be written just a little more verbosely to get 0 per call.
- There's now a fair amount of duplicated code between EngineShoutcast and EngineRecord. Can you pull the current- metadata- detection code out into a common class that they both use? You could even build it into the PlayerInfo singleton class since that already exists.
- Minor nit: EngineRecord: :m_pMetaDataLif e is an int so its prefix should be m_iMetaDataFile
- Maybe pull 5000 out into a constant? UPDATE_RATE_MS or something. also add a comment about what it is
thanks,
RJ