Merge lp:~mixxxdevelopers/mixxx/features_cuefile into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Phillip Whelan
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
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan Approve
Review via email: mp+51662@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

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_pMetaDataLife 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

review: Needs Fixing
2626. By Phillip Whelan

Merging from Trunk.

Revision history for this message
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.

Revision history for this message
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 getCurrentPlayingDeck 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 getCurrentPlayingDeck method thread safe with a Mutex. getCurrentPlayingTrack does not need a Mutex since it calls mutex'd methods and uses no other state itself.

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

I moved all the functionality into PlayefInfo and refactored EngineRecord and EngineShoutCast to use it.

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

Awesome -- looks good for merging. I found these two issues:

1) TrackInfoObject::getId() only works for Mixxx library tracks. It won't work for tracks loaded from Rhythmbox, iTunes, Traktor, or Browse tracks, since all their IDs are -1. This might change in the future, but the best way is to probably just compare the TrackPointers directly. You're guaranteed that for tracks in the Mixxx library, there is only one TrackInfoObject* that exists for it, so QSharedPointer::operator= will work to tell if it's the same track. RB, iTunes, Traktor and Browse don't work this way, but at least they could be made to work that way when/if they ever keep track of the TIO's that they create. I would change all uses of getId() to compare the TrackPointers directly.

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::getCurrentPlayingDeck. All the volume values are integers, while the engine volume values are 0.0 to 1.0 and the crossfader is -1.0 to 1.0, so these will get rounded off in integer conversion. Also, I think the xfvol calculation is slightly off since the orientation controls have non-intuitive values: left is 0, center is 1, right is 2.

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_listCOOrientation[chan]->get() - 1.0 - m_COxfader->get());

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.

review: Approve
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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