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
=== modified file 'mixxx/src/dlgprefrecord.cpp'
--- mixxx/src/dlgprefrecord.cpp 2010-11-01 17:24:48 +0000
+++ mixxx/src/dlgprefrecord.cpp 2011-03-25 05:39:41 +0000
@@ -55,11 +55,14 @@
5555
56 //Connections56 //Connections
57 connect(PushButtonBrowse, SIGNAL(clicked()), this, SLOT(slotBrowseSave()));57 connect(PushButtonBrowse, SIGNAL(clicked()), this, SLOT(slotBrowseSave()));
58 connect(PushButtonBrowseCue, SIGNAL(clicked()), this, SLOT(slotBrowseCueSave()));
58 connect(LineEditRecPath, SIGNAL(returnPressed()), this, SLOT(slotApply()));59 connect(LineEditRecPath, SIGNAL(returnPressed()), this, SLOT(slotApply()));
59 connect(comboBoxEncoding, SIGNAL(activated(int)), this, SLOT(slotRecordPathChange()));60 connect(comboBoxEncoding, SIGNAL(activated(int)), this, SLOT(slotRecordPathChange()));
60 connect(SliderQuality, SIGNAL(valueChanged(int)), this, SLOT(slotSliderQuality()));61 connect(SliderQuality, SIGNAL(valueChanged(int)), this, SLOT(slotSliderQuality()));
61 connect(SliderQuality, SIGNAL(sliderMoved(int)), this, SLOT(slotSliderQuality()));62 connect(SliderQuality, SIGNAL(sliderMoved(int)), this, SLOT(slotSliderQuality()));
62 connect(SliderQuality, SIGNAL(sliderReleased()), this, SLOT(slotSliderQuality()));63 connect(SliderQuality, SIGNAL(sliderReleased()), this, SLOT(slotSliderQuality()));
64 connect(CheckBoxRecordCueFile, SIGNAL(stateChanged(int)), this, SLOT(slotEnableCueFile(int)));
65 connect(LineEditRecPath, SIGNAL(textChanged(QString)), this, SLOT(slotRecordPathChanged(QString)));
6366
64 slotApply();67 slotApply();
65 recordControl->slotSet(RECORD_OFF); //make sure a corrupt config file won't cause us to record constantly68 recordControl->slotSet(RECORD_OFF); //make sure a corrupt config file won't cause us to record constantly
@@ -80,6 +83,45 @@
80 }83 }
81}84}
8285
86void DlgPrefRecord::slotBrowseCueSave()
87{
88 QString encodingFileFilter = QString("CUE file (*.cue)");
89 QString selectedFile = QFileDialog::getSaveFileName(NULL, tr("Save Cue File As..."), config->getValueString(ConfigKey(RECORDING_PREF_KEY,"CuePath")), encodingFileFilter);
90 if (selectedFile.toLower() != "")
91 {
92 if(!selectedFile.toLower().endsWith(".cue"))
93 {
94 selectedFile.append(".cue");
95 }
96 LineEditCuePath->setText( selectedFile );
97 }
98}
99
100void DlgPrefRecord::slotEnableCueFile(int enabled)
101{
102 bool status = enabled ? true : false;
103
104
105 config->set(ConfigKey(RECORDING_PREF_KEY, "CueEnabled"), ConfigValue(CheckBoxRecordCueFile->isChecked()));
106
107 LabelCueFile->setEnabled(status);
108 LineEditCuePath->setEnabled(status);
109 PushButtonBrowseCue->setEnabled(status);
110}
111
112void DlgPrefRecord::slotRecordPathChanged(QString path)
113{
114 QString cuePath = path;
115 int pos;
116
117
118 pos = path.lastIndexOf(".");
119 cuePath.replace(pos, 4, ".cue");
120 cuePath.truncate(pos + 4);
121
122 LineEditCuePath->setText(cuePath);
123}
124
83void DlgPrefRecord::slotSliderQuality()125void DlgPrefRecord::slotSliderQuality()
84{126{
85 updateTextQuality();127 updateTextQuality();
@@ -192,6 +234,7 @@
192void DlgPrefRecord::slotApply()234void DlgPrefRecord::slotApply()
193{235{
194 config->set(ConfigKey(RECORDING_PREF_KEY, "Path"), LineEditRecPath->text());236 config->set(ConfigKey(RECORDING_PREF_KEY, "Path"), LineEditRecPath->text());
237 config->set(ConfigKey(RECORDING_PREF_KEY, "CuePath"), LineEditCuePath->text());
195 setMetaData();238 setMetaData();
196239
197 slotEncoding();240 slotEncoding();
198241
=== modified file 'mixxx/src/dlgprefrecord.h'
--- mixxx/src/dlgprefrecord.h 2008-03-20 03:42:11 +0000
+++ mixxx/src/dlgprefrecord.h 2011-03-25 05:39:41 +0000
@@ -41,6 +41,9 @@
41 void slotApply();41 void slotApply();
42 void slotUpdate();42 void slotUpdate();
43 void slotBrowseSave();43 void slotBrowseSave();
44 void slotBrowseCueSave();
45 void slotEnableCueFile(int);
46 void slotRecordPathChanged(QString);
44 void slotEncoding();47 void slotEncoding();
45 void slotSliderQuality();48 void slotSliderQuality();
46 void slotRecordPathChange();49 void slotRecordPathChange();
4750
=== modified file 'mixxx/src/dlgprefrecorddlg.ui'
--- mixxx/src/dlgprefrecorddlg.ui 2010-11-29 21:23:11 +0000
+++ mixxx/src/dlgprefrecorddlg.ui 2011-03-25 05:39:41 +0000
@@ -239,6 +239,63 @@
239 </widget>239 </widget>
240 </item>240 </item>
241 <item>241 <item>
242 <widget class="QCheckBox" name="CheckBoxRecordCueFile">
243 <property name="text">
244 <string>Create a CUE file</string>
245 </property>
246 </widget>
247 </item>
248 <item>
249 <layout class="QHBoxLayout" name="horizontalLayout">
250 <item>
251 <widget class="QLabel" name="LabelCueFile">
252 <property name="enabled">
253 <bool>false</bool>
254 </property>
255 <property name="sizePolicy">
256 <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
257 <horstretch>0</horstretch>
258 <verstretch>0</verstretch>
259 </sizepolicy>
260 </property>
261 <property name="text">
262 <string>Cue File</string>
263 </property>
264 </widget>
265 </item>
266 <item>
267 <spacer name="horizontalSpacer">
268 <property name="orientation">
269 <enum>Qt::Horizontal</enum>
270 </property>
271 <property name="sizeHint" stdset="0">
272 <size>
273 <width>40</width>
274 <height>20</height>
275 </size>
276 </property>
277 </spacer>
278 </item>
279 <item>
280 <widget class="QLineEdit" name="LineEditCuePath">
281 <property name="enabled">
282 <bool>false</bool>
283 </property>
284 </widget>
285 </item>
286 <item>
287 <widget class="QPushButton" name="PushButtonBrowseCue">
288 <property name="enabled">
289 <bool>false</bool>
290 </property>
291 <property name="text">
292 <string>Browse...</string>
293 </property>
294 </widget>
295 </item>
296 </layout>
297 </item>
298 <item>
242 <spacer>299 <spacer>
243 <property name="orientation">300 <property name="orientation">
244 <enum>Qt::Vertical</enum>301 <enum>Qt::Vertical</enum>
245302
=== modified file 'mixxx/src/engine/engineshoutcast.cpp'
--- mixxx/src/engine/engineshoutcast.cpp 2010-11-04 18:32:26 +0000
+++ mixxx/src/engine/engineshoutcast.cpp 2011-03-25 05:39:41 +0000
@@ -27,9 +27,9 @@
27#include "playerinfo.h"27#include "playerinfo.h"
28#include "trackinfoobject.h"28#include "trackinfoobject.h"
29#ifdef __WINDOWS__29#ifdef __WINDOWS__
30 #include <windows.h>30 #include <windows.h>
31 //sleep on linux assumes seconds where as Sleep on Windows assumes milliseconds31 //sleep on linux assumes seconds where as Sleep on Windows assumes milliseconds
32 #define sleep(x) Sleep(x*1000)32 #define sleep(x) Sleep(x*1000)
33#endif33#endif
3434
35#define TIMEOUT 1035#define TIMEOUT 10
@@ -49,9 +49,6 @@
49 m_pConfig(_config),49 m_pConfig(_config),
50 m_encoder(NULL),50 m_encoder(NULL),
51 m_pUpdateShoutcastFromPrefs(NULL),51 m_pUpdateShoutcastFromPrefs(NULL),
52 m_pCrossfader(NULL),
53 m_pVolume1(NULL),
54 m_pVolume2(NULL),
55 m_shoutMutex(QMutex::Recursive) {52 m_shoutMutex(QMutex::Recursive) {
5653
57 m_pShout = 0;54 m_pShout = 0;
@@ -61,12 +58,6 @@
6158
62 m_bQuit = false;59 m_bQuit = false;
6360
64 m_pCrossfader = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Master]","crossfader")));
65 m_pVolume1 = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Channel1]","volume")));
66 m_pVolume2 = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Channel2]","volume")));
67
68
69
70 m_firstCall = false;61 m_firstCall = false;
71 // Initialize libshout62 // Initialize libshout
72 shout_init();63 shout_init();
@@ -94,16 +85,13 @@
94 QMutexLocker locker(&m_shoutMutex);85 QMutexLocker locker(&m_shoutMutex);
9586
96 if (m_encoder){87 if (m_encoder){
97 m_encoder->flush();88 m_encoder->flush();
9889
99 delete m_encoder;90 delete m_encoder;
100 }91 }
10192
102 delete m_pUpdateShoutcastFromPrefs;93 delete m_pUpdateShoutcastFromPrefs;
103 delete m_pShoutcastNeedUpdateFromPrefs;94 delete m_pShoutcastNeedUpdateFromPrefs;
104 delete m_pCrossfader;
105 delete m_pVolume1;
106 delete m_pVolume2;
10795
108 if (m_pShoutMetaData)96 if (m_pShoutMetaData)
109 shout_metadata_free(m_pShoutMetaData);97 shout_metadata_free(m_pShoutMetaData);
@@ -117,26 +105,26 @@
117{105{
118 QMutexLocker locker(&m_shoutMutex);106 QMutexLocker locker(&m_shoutMutex);
119 if (m_encoder){107 if (m_encoder){
120 m_encoder->flush();108 m_encoder->flush();
121 delete m_encoder;109 delete m_encoder;
122 m_encoder = NULL;110 m_encoder = NULL;
123 }111 }
124112
125 if (m_pShout) {113 if (m_pShout) {
126 shout_close(m_pShout);114 shout_close(m_pShout);
127 return true;115 return true;
128 }116 }
129 return false; //if no connection has been established, nothing can be disconnected117 return false; //if no connection has been established, nothing can be disconnected
130}118}
131bool EngineShoutcast::isConnected()119bool EngineShoutcast::isConnected()
132{120{
133 QMutexLocker locker(&m_shoutMutex);121 QMutexLocker locker(&m_shoutMutex);
134 if (m_pShout) {122 if (m_pShout) {
135 m_iShoutStatus = shout_get_connected(m_pShout);123 m_iShoutStatus = shout_get_connected(m_pShout);
136 if (m_iShoutStatus == SHOUTERR_CONNECTED)124 if (m_iShoutStatus == SHOUTERR_CONNECTED)
137 return true;125 return true;
138 }126 }
139 return false;127 return false;
140}128}
141/*129/*
142 * Update EngineShoutcast values from the preferences.130 * Update EngineShoutcast values from the preferences.
@@ -162,11 +150,11 @@
162 QByteArray baStreamPublic = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"stream_public")).toLatin1();150 QByteArray baStreamPublic = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"stream_public")).toLatin1();
163 QByteArray baBitrate = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"bitrate")).toLatin1();151 QByteArray baBitrate = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"bitrate")).toLatin1();
164152
165 m_baFormat = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"format")).toLatin1();153 m_baFormat = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"format")).toLatin1();
166154
167 m_custom_metadata = (bool)m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"enable_metadata")).toInt();155 m_custom_metadata = (bool)m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"enable_metadata")).toInt();
168 m_baCustom_title = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_title")).toLatin1();156 m_baCustom_title = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_title")).toLatin1();
169 m_baCustom_artist = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_artist")).toLatin1();157 m_baCustom_artist = m_pConfig->getValueString(ConfigKey(SHOUTCAST_PREF_KEY,"custom_artist")).toLatin1();
170158
171 int format;159 int format;
172 int len;160 int len;
@@ -174,22 +162,22 @@
174162
175163
176 if (shout_set_host(m_pShout, baHost.data()) != SHOUTERR_SUCCESS) {164 if (shout_set_host(m_pShout, baHost.data()) != SHOUTERR_SUCCESS) {
177 errorDialog("Error setting hostname!", shout_get_error(m_pShout));165 errorDialog("Error setting hostname!", shout_get_error(m_pShout));
178 return;166 return;
179 }167 }
180168
181 if (shout_set_protocol(m_pShout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) {169 if (shout_set_protocol(m_pShout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) {
182 errorDialog("Error setting protocol!", shout_get_error(m_pShout));170 errorDialog("Error setting protocol!", shout_get_error(m_pShout));
183 return;171 return;
184 }172 }
185173
186 if (shout_set_port(m_pShout, baPort.toUInt()) != SHOUTERR_SUCCESS) {174 if (shout_set_port(m_pShout, baPort.toUInt()) != SHOUTERR_SUCCESS) {
187 errorDialog("Error setting port!", shout_get_error(m_pShout));175 errorDialog("Error setting port!", shout_get_error(m_pShout));
188 return;176 return;
189 }177 }
190178
191 if (shout_set_password(m_pShout, baPassword.data()) != SHOUTERR_SUCCESS) {179 if (shout_set_password(m_pShout, baPassword.data()) != SHOUTERR_SUCCESS) {
192 errorDialog("Error setting password!", shout_get_error(m_pShout));180 errorDialog("Error setting password!", shout_get_error(m_pShout));
193 return;181 return;
194 }182 }
195 if (shout_set_mount(m_pShout, baMountPoint.data()) != SHOUTERR_SUCCESS) {183 if (shout_set_mount(m_pShout, baMountPoint.data()) != SHOUTERR_SUCCESS) {
@@ -198,22 +186,22 @@
198 }186 }
199187
200 if (shout_set_user(m_pShout, baLogin.data()) != SHOUTERR_SUCCESS) {188 if (shout_set_user(m_pShout, baLogin.data()) != SHOUTERR_SUCCESS) {
201 errorDialog("Error setting username!", shout_get_error(m_pShout));189 errorDialog("Error setting username!", shout_get_error(m_pShout));
202 return;190 return;
203 }191 }
204 if (shout_set_name(m_pShout, baStreamName.data()) != SHOUTERR_SUCCESS) {192 if (shout_set_name(m_pShout, baStreamName.data()) != SHOUTERR_SUCCESS) {
205 errorDialog("Error setting stream name!", shout_get_error(m_pShout));193 errorDialog("Error setting stream name!", shout_get_error(m_pShout));
206 return;194 return;
207 }195 }
208 if (shout_set_description(m_pShout, baStreamDesc.data()) != SHOUTERR_SUCCESS) {196 if (shout_set_description(m_pShout, baStreamDesc.data()) != SHOUTERR_SUCCESS) {
209 errorDialog("Error setting stream description!", shout_get_error(m_pShout));197 errorDialog("Error setting stream description!", shout_get_error(m_pShout));
210 return;198 return;
211 }199 }
212 if (shout_set_genre(m_pShout, baStreamGenre.data()) != SHOUTERR_SUCCESS) {200 if (shout_set_genre(m_pShout, baStreamGenre.data()) != SHOUTERR_SUCCESS) {
213 errorDialog("Error setting stream genre!", shout_get_error(m_pShout));201 errorDialog("Error setting stream genre!", shout_get_error(m_pShout));
214 return;202 return;
215 }203 }
216 if (shout_set_url(m_pShout, baStreamWebsite.data()) != SHOUTERR_SUCCESS) {204 if (shout_set_url(m_pShout, baStreamWebsite.data()) != SHOUTERR_SUCCESS) {
217 errorDialog("Error setting stream url!", shout_get_error(m_pShout));205 errorDialog("Error setting stream url!", shout_get_error(m_pShout));
218 return;206 return;
219 }207 }
@@ -251,24 +239,24 @@
251 } else if ( ! qstricmp(baServerType.data(), "Icecast 1")) {239 } else if ( ! qstricmp(baServerType.data(), "Icecast 1")) {
252 protocol = SHOUT_PROTOCOL_XAUDIOCAST;240 protocol = SHOUT_PROTOCOL_XAUDIOCAST;
253 } else {241 } else {
254 errorDialog("Error: unknown server protocol!", shout_get_error(m_pShout));242 errorDialog("Error: unknown server protocol!", shout_get_error(m_pShout));
255 return;243 return;
256 }244 }
257245
258 if (( protocol == SHOUT_PROTOCOL_ICY ) && ( format != SHOUT_FORMAT_MP3)) {246 if (( protocol == SHOUT_PROTOCOL_ICY ) && ( format != SHOUT_FORMAT_MP3)) {
259 errorDialog("Error: libshout only supports Shoutcast with MP3 format!", shout_get_error(m_pShout));247 errorDialog("Error: libshout only supports Shoutcast with MP3 format!", shout_get_error(m_pShout));
260 return;248 return;
261 }249 }
262250
263 if (shout_set_protocol(m_pShout, protocol) != SHOUTERR_SUCCESS) {251 if (shout_set_protocol(m_pShout, protocol) != SHOUTERR_SUCCESS) {
264 errorDialog("Error setting protocol!", shout_get_error(m_pShout));252 errorDialog("Error setting protocol!", shout_get_error(m_pShout));
265 return;253 return;
266 }254 }
267255
268 // Initialize m_encoder256 // Initialize m_encoder
269 if(m_encoder) {257 if(m_encoder) {
270 delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate258 delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate
271 }259 }
272 if ( ! qstrcmp(m_baFormat, "MP3")) {260 if ( ! qstrcmp(m_baFormat, "MP3")) {
273 m_encoder = new EncoderMp3(this);261 m_encoder = new EncoderMp3(this);
274262
@@ -281,11 +269,11 @@
281 return;269 return;
282 }270 }
283 if (m_encoder->initEncoder(baBitrate.toInt()) < 0) {271 if (m_encoder->initEncoder(baBitrate.toInt()) < 0) {
284 //e.g., if lame is not found272 //e.g., if lame is not found
285 //init m_encoder itself will display a message box273 //init m_encoder itself will display a message box
286 qDebug() << "**** Encoder init failed";274 qDebug() << "**** Encoder init failed";
287 delete m_encoder;275 delete m_encoder;
288 m_encoder = NULL;276 m_encoder = NULL;
289 }277 }
290278
291}279}
@@ -304,19 +292,19 @@
304 m_iShoutFailures = 0;292 m_iShoutFailures = 0;
305 // set to a high number to automatically update the metadata293 // set to a high number to automatically update the metadata
306 // on the first change294 // on the first change
307 m_pMetaDataLife = 31337;295 m_iMetaDataLife = 31337;
308 //If static metadata is available, we only need to send metadata one time296 //If static metadata is available, we only need to send metadata one time
309 m_firstCall = false;297 m_firstCall = false;
310298
311 /*Check if m_encoder is initalized299 /*Check if m_encoder is initalized
312 * Encoder is initalized in updateFromPreferences which is called always before serverConnect()300 * Encoder is initalized in updateFromPreferences which is called always before serverConnect()
313 * If m_encoder is NULL, then we propably want to use MP3 streaming, however, lame could not be found301 * If m_encoder is NULL, then we propably want to use MP3 streaming, however, lame could not be found
314 * It does not make sense to connect302 * It does not make sense to connect
315 */303 */
316 if(m_encoder == NULL){304 if(m_encoder == NULL){
317 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0"));305 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0"));
318 return false;306 return false;
319 }307 }
320 const int iMaxTries = 3;308 const int iMaxTries = 3;
321 while (!m_bQuit && m_iShoutFailures < iMaxTries) {309 while (!m_bQuit && m_iShoutFailures < iMaxTries) {
322 if (m_pShout)310 if (m_pShout)
@@ -338,8 +326,8 @@
338 if (m_iShoutFailures == iMaxTries) {326 if (m_iShoutFailures == iMaxTries) {
339 if (m_pShout)327 if (m_pShout)
340 shout_close(m_pShout);328 shout_close(m_pShout);
341 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0"));329 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0"));
342 330
343 }331 }
344 if (m_bQuit) {332 if (m_bQuit) {
345 if (m_pShout)333 if (m_pShout)
@@ -348,23 +336,23 @@
348 }336 }
349337
350 m_iShoutFailures = 0;338 m_iShoutFailures = 0;
351 int timeout = 0;339 int timeout = 0;
352 while (m_iShoutStatus == SHOUTERR_BUSY && timeout < TIMEOUT) {340 while (m_iShoutStatus == SHOUTERR_BUSY && timeout < TIMEOUT) {
353 qDebug() << "Connection pending. Sleeping...";341 qDebug() << "Connection pending. Sleeping...";
354 sleep(1);342 sleep(1);
355 m_iShoutStatus = shout_get_connected(m_pShout);343 m_iShoutStatus = shout_get_connected(m_pShout);
356 ++ timeout;344 ++ timeout;
357 }345 }
358 if (m_iShoutStatus == SHOUTERR_CONNECTED) {346 if (m_iShoutStatus == SHOUTERR_CONNECTED) {
359 qDebug() << "***********Connected to Shoutcast server...";347 qDebug() << "***********Connected to Shoutcast server...";
360 return true;348 return true;
361 }349 }
362 //otherwise disable shoutcast in preferences350 //otherwise disable shoutcast in preferences
363 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0"));351 m_pConfig->set(ConfigKey("[Shoutcast]","enabled"),ConfigValue("0"));
364 if(m_pShout){352 if(m_pShout){
365 shout_close(m_pShout);353 shout_close(m_pShout);
366 //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."));354 //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."));
367 }355 }
368356
369 return false;357 return false;
370}358}
@@ -431,111 +419,60 @@
431void EngineShoutcast::process(const CSAMPLE *, const CSAMPLE *pOut, const int iBufferSize)419void EngineShoutcast::process(const CSAMPLE *, const CSAMPLE *pOut, const int iBufferSize)
432{420{
433 QMutexLocker locker(&m_shoutMutex);421 QMutexLocker locker(&m_shoutMutex);
434 //Check to see if Shoutcast is enabled, and pass the samples off to be broadcast if necessary.422 //Check to see if Shoutcast is enabled, and pass the samples off to be broadcast if necessary.
435 bool prefEnabled = (m_pConfig->getValueString(ConfigKey("[Shoutcast]","enabled")).toInt() == 1);423 bool prefEnabled = (m_pConfig->getValueString(ConfigKey("[Shoutcast]","enabled")).toInt() == 1);
436424
437 if (prefEnabled) {425 if (prefEnabled) {
438 if(!isConnected()){426 if(!isConnected()){
439 //Initialize the m_pShout structure with the info from Mixxx's m_shoutcast preferences.427 //Initialize the m_pShout structure with the info from Mixxx's m_shoutcast preferences.
440 updateFromPreferences();428 updateFromPreferences();
441429
442 if(serverConnect()){430 if(serverConnect()){
443 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();431 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
444 props->setType(DLG_INFO);432 props->setType(DLG_INFO);
445 props->setTitle(tr("Live broadcasting"));433 props->setTitle(tr("Live broadcasting"));
446 props->setText(tr("Mixxx has successfully connected to the shoutcast server"));434 props->setText(tr("Mixxx has successfully connected to the shoutcast server"));
447 ErrorDialogHandler::instance()->requestErrorDialog(props);435 ErrorDialogHandler::instance()->requestErrorDialog(props);
448 }436 }
449 else{437 else{
450 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."));438 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."));
451439
452 }440 }
453 }441 }
454 //send to shoutcast, if connection has been established442 //send to shoutcast, if connection has been established
455 if (m_iShoutStatus != SHOUTERR_CONNECTED)443 if (m_iShoutStatus != SHOUTERR_CONNECTED)
456 return;444 return;
457445
458 if (iBufferSize > 0 && m_encoder){446 if (iBufferSize > 0 && m_encoder){
459 m_encoder->encodeBuffer(pOut, iBufferSize); //encode and send to shoutcast447 m_encoder->encodeBuffer(pOut, iBufferSize); //encode and send to shoutcast
460 }448 }
461 //Check if track has changed and submit its new metadata to shoutcast449 //Check if track has changed and submit its new metadata to shoutcast
462 if (metaDataHasChanged())450 if (metaDataHasChanged())
463 updateMetaData();451 updateMetaData();
464452
465 if (m_pUpdateShoutcastFromPrefs->get() > 0.0f){453 if (m_pUpdateShoutcastFromPrefs->get() > 0.0f){
466 /*454 /*
467 * You cannot change bitrate, hostname, etc while connected to a stream455 * You cannot change bitrate, hostname, etc while connected to a stream
468 */456 */
469 serverDisconnect();457 serverDisconnect();
470 updateFromPreferences();458 updateFromPreferences();
471 serverConnect();459 serverConnect();
472 }460 }
473 }461 }
474 //if shoutcast is disabled462 //if shoutcast is disabled
475 else{463 else{
476 if(isConnected()){464 if(isConnected()){
477 serverDisconnect();465 serverDisconnect();
478 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();466 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
479 props->setType(DLG_INFO);467 props->setType(DLG_INFO);
480 props->setTitle(tr("Live broadcasting"));468 props->setTitle(tr("Live broadcasting"));
481 props->setText(tr("Mixxx has successfully disconnected to the shoutcast server"));469 props->setText(tr("Mixxx has successfully disconnected to the shoutcast server"));
482470
483 ErrorDialogHandler::instance()->requestErrorDialog(props);471 ErrorDialogHandler::instance()->requestErrorDialog(props);
484 }472 }
485 }
486}
487
488/*
489 * Algorithm which simply flips the lowest and/or second lowest bits,
490 * bits 1 and 2, to represent which track is active and returns the result.
491 */
492int EngineShoutcast::getActiveTracks()
493{
494 int tracks = 0;
495
496
497 if (ControlObject::getControl(ConfigKey("[Channel1]","play"))->get()==1.) tracks |= 1;
498 if (ControlObject::getControl(ConfigKey("[Channel2]","play"))->get()==1.) tracks |= 2;
499
500 if (tracks == 0)
501 return 0;
502
503 // Detect the dominant track by checking the crossfader and volume levels
504 if ((tracks & 1) && (tracks & 2)) {
505
506 if ((m_pVolume1->get() == 0) && (m_pVolume2->get() == 0))
507 return 0;
508
509 if (m_pVolume2->get() == 0) {
510 tracks = 1;
511 }
512 else if ( m_pVolume1->get() == 0) {
513 tracks = 2;
514 }
515 // allow a bit of leeway with the crossfader
516 else if ((m_pCrossfader->get() < 0.05) && (m_pCrossfader->get() > -0.05)) {
517
518 if (m_pVolume1->get() > m_pVolume2->get()) {
519 tracks = 1;
520 }
521 else if (m_pVolume1->get() < m_pVolume2->get()) {
522 tracks = 2;
523 }
524
525 }
526 else if ( m_pCrossfader->get() < -0.05 ) {
527 tracks = 1;
528 }
529 else if ( m_pCrossfader->get() > 0.05 ) {
530 tracks = 2;
531 }
532
533 }473 }
534
535 return tracks;
536}474}
537475
538
539/*476/*
540 * Check if the metadata has changed since the previous check.477 * Check if the metadata has changed since the previous check.
541 * We also check when was the last check performed to avoid using478 * We also check when was the last check performed to avoid using
@@ -545,50 +482,35 @@
545bool EngineShoutcast::metaDataHasChanged()482bool EngineShoutcast::metaDataHasChanged()
546{483{
547 QMutexLocker locker(&m_shoutMutex);484 QMutexLocker locker(&m_shoutMutex);
548 int tracks;485 TrackPointer pTrack;
549 TrackPointer newMetaData;486
550 bool changed = false;487
551488 if ( m_iMetaDataLife < 16 ) {
552489 m_iMetaDataLife++;
553 if ( m_pMetaDataLife < 16 ) {490 return false;
554 m_pMetaDataLife++;491 }
555 return false;492
556 }493 m_iMetaDataLife = 0;
557494
558 m_pMetaDataLife = 0;495
559496 pTrack = PlayerInfo::Instance().getCurrentPlayingTrack();
560497 if ( !pTrack )
561 tracks = getActiveTracks();498 return false;
562499
563 switch (tracks)500 if ( m_pMetaData ) {
564 {501 if ((pTrack->getId() == -1) || (m_pMetaData->getId() == -1)) {
565 case 0:502 if ((pTrack->getArtist() == m_pMetaData->getArtist()) &&
566 // no tracks are playing503 (pTrack->getTitle() == m_pMetaData->getArtist())) {
567 // we should set the metadata to nothing504 return false;
568 break;505 }
569 case 1:506 }
570 // track 1 is active507 else if (pTrack->getId() == m_pMetaData->getId()) {
571 newMetaData = PlayerInfo::Instance().getTrackInfo("[Channel1]");508 return false;
572 if (newMetaData != m_pMetaData)509 }
573 {510 }
574 m_pMetaData = newMetaData;511
575 changed = true;512 m_pMetaData = pTrack;
576 }513 return true;
577 break;
578 case 2:
579 // track 2 is active
580 newMetaData = PlayerInfo::Instance().getTrackInfo("[Channel2]");
581 if (newMetaData != m_pMetaData)
582 {
583 m_pMetaData = newMetaData;
584 changed = true;
585 }
586 break;
587 case 3:
588 // both tracks are active, just stick with it for now
589 break;
590 }
591 return changed;
592}514}
593515
594/*516/*
@@ -603,51 +525,48 @@
603 return;525 return;
604526
605 QByteArray baSong = "";527 QByteArray baSong = "";
606 /**528 /**
607 * If track has changed and static metadata is disabled529 * If track has changed and static metadata is disabled
608 * Send new metadata to shoutcast!530 * Send new metadata to shoutcast!
609 * This works only for MP3 streams properly as stated in comments, see shout.h531 * This works only for MP3 streams properly as stated in comments, see shout.h
610 * WARNING: Changing OGG metadata dynamically by using shout_set_metadata532 * WARNING: Changing OGG metadata dynamically by using shout_set_metadata
611 * will cause stream interruptions to listeners533 * will cause stream interruptions to listeners
612 *534 *
613 * Also note: Do not try to include Vorbis comments in OGG packages and send them to stream.535 * Also note: Do not try to include Vorbis comments in OGG packages and send them to stream.
614 * This was done in EncoderVorbis previously and caused interruptions on track change as well536 * This was done in EncoderVorbis previously and caused interruptions on track change as well
615 * which sounds awful to listeners.537 * which sounds awful to listeners.
616538 * To conlcude: Only write OGG metadata one time, i.e., if static metadata is used.
617 * To conlcude: Only write OGG metadata one time, i.e., if static metadata is used.539 */
618 */
619540
620541
621 //If we use MP3 streaming and want dynamic metadata changes542 //If we use MP3 streaming and want dynamic metadata changes
622 if(!m_custom_metadata && !qstrcmp(m_baFormat, "MP3")){543 if(!m_custom_metadata && !qstrcmp(m_baFormat, "MP3")){
623 if (m_pMetaData != NULL) {544 if (m_pMetaData != NULL) {
624 // convert QStrings to char*s545 // convert QStrings to char*s
625 QByteArray baArtist = m_pMetaData->getArtist().toLatin1();546 QByteArray baArtist = m_pMetaData->getArtist().toLatin1();
626 QByteArray baTitle = m_pMetaData->getTitle().toLatin1();547 QByteArray baTitle = m_pMetaData->getTitle().toLatin1();
627 548
628 if (baArtist.isEmpty())549 if (baArtist.isEmpty())
629 baSong = baTitle;550 baSong = baTitle;
630 else551 else
631 baSong = baArtist + " - " + baTitle;552 baSong = baArtist + " - " + baTitle;
632553
633 /** Update metadata */554 /** Update metadata */
634 shout_metadata_add(m_pShoutMetaData, "song", baSong.data());555 shout_metadata_add(m_pShoutMetaData, "song", baSong.data());
635 shout_set_metadata(m_pShout, m_pShoutMetaData);556 shout_set_metadata(m_pShout, m_pShoutMetaData);
636 }557 }
637 }558 }
638 //Otherwise we might use static metadata559 //Otherwise we might use static metadata
639 else{560 else{
640 /** If we use static metadata, we only need to call the following line once **/561 /** If we use static metadata, we only need to call the following line once **/
641 if(m_custom_metadata && !m_firstCall){562 if(m_custom_metadata && !m_firstCall){
642 baSong = m_baCustom_artist + " - " + m_baCustom_title;563 baSong = m_baCustom_artist + " - " + m_baCustom_title;
643 /** Update metadata */564 /** Update metadata */
644 shout_metadata_add(m_pShoutMetaData, "song", baSong.data());565 shout_metadata_add(m_pShoutMetaData, "song", baSong.data());
645 shout_set_metadata(m_pShout, m_pShoutMetaData);566 shout_set_metadata(m_pShout, m_pShoutMetaData);
646 m_firstCall = true;567 m_firstCall = true;
647 }568 }
648 }569 }
649
650
651}570}
652/* -------- ------------------------------------------------------571/* -------- ------------------------------------------------------
653Purpose: Common error dialog creation code for run-time exceptions572Purpose: Common error dialog creation code for run-time exceptions
654573
=== modified file 'mixxx/src/engine/engineshoutcast.h'
--- mixxx/src/engine/engineshoutcast.h 2010-09-07 07:50:15 +0000
+++ mixxx/src/engine/engineshoutcast.h 2011-03-25 05:39:41 +0000
@@ -66,16 +66,13 @@
66 TrackPointer m_pMetaData;66 TrackPointer m_pMetaData;
67 shout_t *m_pShout;67 shout_t *m_pShout;
68 shout_metadata_t *m_pShoutMetaData;68 shout_metadata_t *m_pShoutMetaData;
69 int m_pMetaDataLife;69 int m_iMetaDataLife;
70 long m_iShoutStatus;70 long m_iShoutStatus;
71 long m_iShoutFailures;71 long m_iShoutFailures;
72 ConfigObject<ConfigValue> *m_pConfig;72 ConfigObject<ConfigValue> *m_pConfig;
73 Encoder *m_encoder;73 Encoder *m_encoder;
74 ControlObject* m_pShoutcastNeedUpdateFromPrefs;74 ControlObject* m_pShoutcastNeedUpdateFromPrefs;
75 ControlObjectThreadMain* m_pUpdateShoutcastFromPrefs;75 ControlObjectThreadMain* m_pUpdateShoutcastFromPrefs;
76 ControlObjectThread* m_pCrossfader;
77 ControlObjectThread* m_pVolume1;
78 ControlObjectThread* m_pVolume2;
79 volatile bool m_bQuit;76 volatile bool m_bQuit;
80 QMutex m_shoutMutex;77 QMutex m_shoutMutex;
81 /** static metadata according to prefereneces **/78 /** static metadata according to prefereneces **/
8279
=== modified file 'mixxx/src/playerinfo.cpp'
--- mixxx/src/playerinfo.cpp 2010-10-24 09:50:11 +0000
+++ mixxx/src/playerinfo.cpp 2011-03-25 05:39:41 +0000
@@ -17,6 +17,45 @@
17#include <QMutexLocker>17#include <QMutexLocker>
1818
19#include "playerinfo.h"19#include "playerinfo.h"
20#include "controlobject.h"
21#include "controlobjectthread.h"
22#include "engine/enginexfader.h"
23
24PlayerInfo::PlayerInfo()
25{
26 int i;
27 m_iNumDecks = ControlObject::getControl(ConfigKey("[Master]","num_decks"))->get();
28
29 for (i = 1; i <= m_iNumDecks; i++) {
30 QString chan = QString("[Channel%1]").arg(i);
31
32 m_listCOPlay[chan] = new ControlObjectThread(ControlObject::getControl(ConfigKey(chan, "play")));
33 m_listCOVolume[chan] = new ControlObjectThread(ControlObject::getControl(ConfigKey(chan, "volume")));
34 m_listCOOrientation[chan] = new ControlObjectThread(ControlObject::getControl(ConfigKey(chan, "orientation")));
35 m_listCOpregain[chan] = new ControlObjectThread(ControlObject::getControl(ConfigKey(chan, "pregain")));
36 }
37
38 m_COxfader = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Master]","crossfader")));
39}
40
41PlayerInfo::~PlayerInfo()
42{
43 int i;
44
45
46 m_loadedTrackMap.clear();
47
48 for (i = 1; i <= m_iNumDecks; i++) {
49 QString chan = QString("[Channel%1]").arg(i);
50
51 delete m_listCOPlay[chan];
52 delete m_listCOVolume[chan];
53 delete m_listCOOrientation[chan];
54 delete m_listCOpregain[chan];
55 }
56
57 delete m_COxfader;
58}
2059
21PlayerInfo &PlayerInfo::Instance()60PlayerInfo &PlayerInfo::Instance()
22{61{
@@ -24,14 +63,6 @@
24 return playerInfo;63 return playerInfo;
25}64}
2665
27PlayerInfo::PlayerInfo() {
28}
29
30PlayerInfo::~PlayerInfo()
31{
32 m_loadedTrackMap.clear();
33}
34
35TrackPointer PlayerInfo::getTrackInfo(QString group)66TrackPointer PlayerInfo::getTrackInfo(QString group)
36{67{
37 QMutexLocker locker(&m_mutex);68 QMutexLocker locker(&m_mutex);
@@ -48,3 +79,61 @@
48 QMutexLocker locker(&m_mutex);79 QMutexLocker locker(&m_mutex);
49 m_loadedTrackMap[group] = track;80 m_loadedTrackMap[group] = track;
50}81}
82
83int PlayerInfo::getCurrentPlayingDeck()
84{
85 QMutexLocker locker(&m_mutex);
86 int MaxVolume = 0;
87 int MaxDeck = 0;
88 int i;
89
90
91 for (i = 1; i <= m_iNumDecks; i++) {
92 QString chan = QString("[Channel%1]").arg(i);
93 float fvol;
94 float xfl, xfr, xfvol;
95 float dvol;
96 int orient;
97
98 if ( m_listCOPlay[chan]->get() == 0.0 )
99 continue;
100
101 if ( m_listCOpregain[chan]->get() <= 0.5 )
102 continue;
103
104 if ((fvol = m_listCOVolume[chan]->get()) == 0.0 )
105 continue;
106
107 EngineXfader::getXfadeGains(xfl, xfr, m_COxfader->get(), 1.0, 0.0);
108
109 // Orientation goes: left is 0, center is 1, right is 2.
110 // Leave math out of it...
111 orient = m_listCOOrientation[chan]->get();
112 if ( orient == 0 )
113 xfvol = xfl;
114 else if ( orient == 2 )
115 xfvol = xfr;
116 else
117 xfvol = 1;
118
119 dvol = fvol * xfvol;
120 if ( dvol > MaxVolume ) {
121 MaxDeck = i;
122 MaxVolume = dvol;
123 }
124 }
125
126 return MaxDeck;
127}
128
129TrackPointer PlayerInfo::getCurrentPlayingTrack()
130{
131 int deck = getCurrentPlayingDeck();
132 if ( deck ) {
133 QString chan = QString("[Channel%1]").arg(deck);
134 return getTrackInfo(chan);
135 }
136
137 return TrackPointer();
138}
139
51140
=== modified file 'mixxx/src/playerinfo.h'
--- mixxx/src/playerinfo.h 2010-10-24 09:50:11 +0000
+++ mixxx/src/playerinfo.h 2011-03-25 05:39:41 +0000
@@ -21,6 +21,9 @@
21#include <QMutex>21#include <QMutex>
22#include <QMap>22#include <QMap>
2323
24
25class ControlObjectThread;
26
24#include "trackinfoobject.h"27#include "trackinfoobject.h"
2528
26class PlayerInfo : public QObject29class PlayerInfo : public QObject
@@ -30,13 +33,21 @@
30 static PlayerInfo &Instance();33 static PlayerInfo &Instance();
31 TrackPointer getTrackInfo(QString group);34 TrackPointer getTrackInfo(QString group);
32 void setTrackInfo(QString group, TrackPointer trackInfoObj);35 void setTrackInfo(QString group, TrackPointer trackInfoObj);
36 int getCurrentPlayingDeck();
37 TrackPointer getCurrentPlayingTrack();
33 private:38 private:
34 PlayerInfo();39 PlayerInfo();
35 ~PlayerInfo();40 ~PlayerInfo();
36 PlayerInfo(PlayerInfo const&);41 PlayerInfo(PlayerInfo const&);
37 PlayerInfo &operator= (PlayerInfo const&);42 PlayerInfo &operator= (PlayerInfo const&);
38 QMutex m_mutex;43 QMutex m_mutex;
44 int m_iNumDecks;
45 ControlObjectThread* m_COxfader;
39 QMap<QString, TrackPointer> m_loadedTrackMap;46 QMap<QString, TrackPointer> m_loadedTrackMap;
47 QMap<QString, ControlObjectThread*> m_listCOPlay;
48 QMap<QString, ControlObjectThread*> m_listCOVolume;
49 QMap<QString, ControlObjectThread*> m_listCOOrientation;
50 QMap<QString, ControlObjectThread*> m_listCOpregain;
40};51};
4152
42#endif53#endif
4354
=== modified file 'mixxx/src/recording/enginerecord.cpp'
--- mixxx/src/recording/enginerecord.cpp 2010-11-01 17:24:48 +0000
+++ mixxx/src/recording/enginerecord.cpp 2011-03-25 05:39:41 +0000
@@ -21,6 +21,7 @@
21#include "configobject.h"21#include "configobject.h"
22#include "controlobjectthread.h"22#include "controlobjectthread.h"
23#include "controlobject.h"23#include "controlobject.h"
24#include "trackinfoobject.h"
24#include "dlgprefrecord.h"25#include "dlgprefrecord.h"
25#ifdef __SHOUTCAST__26#ifdef __SHOUTCAST__
26#include "encodervorbis.h"27#include "encodervorbis.h"
@@ -47,10 +48,12 @@
47 m_recReady = new ControlObjectThread(m_recReadyCO);48 m_recReady = new ControlObjectThread(m_recReadyCO);
48 m_samplerate = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Master]", "samplerate")));49 m_samplerate = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Master]", "samplerate")));
4950
51 m_iMetaDataLife = 0;
50}52}
5153
52EngineRecord::~EngineRecord()54EngineRecord::~EngineRecord()
53{55{
56 closeCueFile();
54 closeFile();57 closeFile();
55 if(m_recReadyCO) delete m_recReadyCO;58 if(m_recReadyCO) delete m_recReadyCO;
56 if(m_recReady) delete m_recReady;59 if(m_recReady) delete m_recReady;
@@ -67,6 +70,8 @@
67 m_baTitle = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Title")).toLatin1();70 m_baTitle = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Title")).toLatin1();
68 m_baAuthor = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Author")).toLatin1();71 m_baAuthor = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Author")).toLatin1();
69 m_baAlbum = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Album")).toLatin1();72 m_baAlbum = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Album")).toLatin1();
73 m_cuefilename = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "CuePath")).toLatin1();
74 m_bCueIsEnabled = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "CueEnabled")).toInt();
7075
71 if(m_encoder){76 if(m_encoder){
72 delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate77 delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate
@@ -112,8 +117,48 @@
112117
113}118}
114119
120/*
121 * Check if the metadata has changed since the previous check.
122 * We also check when was the last check performed to avoid using
123 * too much CPU and as well to avoid changing the metadata during
124 * scratches.
125 */
126bool EngineRecord::metaDataHasChanged()
127{
128 TrackPointer pTrack;
129
130
131 if ( m_iMetaDataLife < 16 ) {
132 m_iMetaDataLife++;
133 return false;
134 }
135 m_iMetaDataLife = 0;
136
137 pTrack = PlayerInfo::Instance().getCurrentPlayingTrack();
138 if ( !pTrack )
139 return false;
140
141 if ( m_pCurrentTrack ) {
142 if ((pTrack->getId() == -1) || (m_pCurrentTrack->getId() == -1)) {
143 if ((pTrack->getArtist() == m_pCurrentTrack->getArtist()) &&
144 (pTrack->getTitle() == m_pCurrentTrack->getArtist())) {
145 return false;
146 }
147 }
148 else if (pTrack->getId() == m_pCurrentTrack->getId()) {
149 return false;
150 }
151 }
152
153 m_pCurrentTrack = pTrack;
154 return true;
155}
156
115void EngineRecord::process(const CSAMPLE * pIn, const CSAMPLE * pOut, const int iBufferSize)157void EngineRecord::process(const CSAMPLE * pIn, const CSAMPLE * pOut, const int iBufferSize)
116{158{
159 // Calculate the latency of this buffer
160 m_dLatency = (double)iBufferSize / m_samplerate->get();
161
117 //if recording is disabled162 //if recording is disabled
118 if(m_recReady->get() == RECORD_OFF){163 if(m_recReady->get() == RECORD_OFF){
119 if(fileOpen()){164 if(fileOpen()){
@@ -126,6 +171,12 @@
126 if(openFile()){171 if(openFile()){
127 qDebug("Setting record flag to: ON");172 qDebug("Setting record flag to: ON");
128 m_recReady->slotSet(RECORD_ON);173 m_recReady->slotSet(RECORD_ON);
174
175 if ( m_bCueIsEnabled ) {
176 openCueFile();
177 m_cuesamplepos = 0;
178 m_cuetrack = 0;
179 }
129 }180 }
130 else{ //Maybe the encoder could not be initialized181 else{ //Maybe the encoder could not be initialized
131 qDebug("Setting record flag to: OFF");182 qDebug("Setting record flag to: OFF");
@@ -139,14 +190,65 @@
139 sf_write_float(m_sndfile, pIn, iBufferSize);190 sf_write_float(m_sndfile, pIn, iBufferSize);
140 }191 }
141 else{192 else{
142 if(!m_encoder) return;193 if ( m_encoder) {
143 //Compress audio. Encoder will call method 'write()' below to write a file stream194 //Compress audio. Encoder will call method 'write()' below to write a file stream
144 m_encoder->encodeBuffer(pIn, iBufferSize);195 m_encoder->encodeBuffer(pIn, iBufferSize);
145 }196 }
197 }
198
199 if ( m_bCueIsEnabled ) {
200
201 if ( metaDataHasChanged()) {
202 m_cuetrack++;
203 writeCueLine();
204 m_cuefile.flush();
205 }
206
207 m_cuesamplepos += iBufferSize;
208
209 }
210
146 }211 }
147212
148213}
149}214
215void EngineRecord::writeCueLine()
216{
217 // account for multiple channels
218 unsigned long samplerate = m_samplerate->get() * 2;
219 // CDDA is specified as having 75 frames a second
220 unsigned long frames = ((unsigned long)
221 ((m_cuesamplepos / (samplerate / 75)))
222 % 75);
223
224 unsigned long seconds = ((unsigned long)
225 (m_cuesamplepos / samplerate)
226 % 60 );
227
228 unsigned long minutes = m_cuesamplepos / (samplerate * 60);
229
230 m_cuefile.write(QString(" TRACK %1 AUDIO\n")
231 .arg((double)m_cuetrack, 2, 'f', 0, '0')
232 .toLatin1()
233 );
234
235 m_cuefile.write(QString(" TITLE %1\n")
236 .arg(m_pCurrentTrack->getTitle()).toLatin1());
237 m_cuefile.write(QString(" PERFORMER %1\n")
238 .arg(m_pCurrentTrack->getArtist()).toLatin1());
239
240 // Woefully inaccurate (at the seconds level anyways).
241 // We'd need a signal fired state tracker
242 // for the track detection code.
243 m_cuefile.write(QString(" INDEX 01 %1:%2:%3\n")
244 .arg((double)minutes, 2, 'f', 0, '0')
245 .arg((double)seconds, 2, 'f', 0, '0')
246 .arg((double)frames, 2, 'f', 0, '0')
247 .toLatin1()
248 );
249
250}
251
150/** encoder will call this method to write compressed audio **/252/** encoder will call this method to write compressed audio **/
151void EngineRecord::write(unsigned char *header, unsigned char *body,253void EngineRecord::write(unsigned char *header, unsigned char *body,
152 int headerLen, int bodyLen)254 int headerLen, int bodyLen)
@@ -232,9 +334,44 @@
232 ErrorDialogHandler::instance()->requestErrorDialog(props);334 ErrorDialogHandler::instance()->requestErrorDialog(props);
233 return false;335 return false;
234 }336 }
235 return true;337
236338 return true;
237}339}
340
341bool EngineRecord::openCueFile() {
342 if ( m_cuefilename.length() <= 0 )
343 {
344 return false;
345 }
346
347 qDebug() << "Opening Cue File:" << m_cuefilename;
348 m_cuefile.setFileName(m_cuefilename);
349 m_cuefile.open(QIODevice::WriteOnly);
350
351 if ( m_baAuthor.length() > 0 ) {
352 m_cuefile.write(QString("PERFORMER \"%1\"\n")
353 .arg(QString(m_baAuthor).replace(QString("\""), QString("\\\"")))
354 .toLatin1()
355 );
356 }
357
358 if ( m_baTitle.length() > 0 ) {
359 m_cuefile.write(QString("TITLE \"%1\"\n")
360 .arg(QString(m_baTitle).replace(QString("\""), QString("\\\"")))
361 .toLatin1()
362 );
363 }
364
365 m_cuefile.write(QString("FILE \"%1\" %2%3\n")
366 .arg(QString(m_filename).replace(QString("\""), QString("\\\"")))
367 .arg(QString(m_Encoding).toUpper())
368 .arg((m_Encoding == ENCODING_WAVE ? 'E' : ' '))
369 .toLatin1()
370 );
371
372 return true;
373}
374
238void EngineRecord::closeFile(){375void EngineRecord::closeFile(){
239 if(m_Encoding == ENCODING_WAVE || m_Encoding == ENCODING_AIFF){376 if(m_Encoding == ENCODING_WAVE || m_Encoding == ENCODING_AIFF){
240 if(m_sndfile != NULL){377 if(m_sndfile != NULL){
@@ -255,3 +392,8 @@
255 }392 }
256}393}
257394
395void EngineRecord::closeCueFile() {
396 if ( m_cuefile.handle() != -1) {
397 m_cuefile.close();
398 }
399}
258400
=== modified file 'mixxx/src/recording/enginerecord.h'
--- mixxx/src/recording/enginerecord.h 2010-11-01 17:24:48 +0000
+++ mixxx/src/recording/enginerecord.h 2011-03-25 05:39:41 +0000
@@ -29,6 +29,8 @@
29#include "encoder.h"29#include "encoder.h"
30#include "errordialoghandler.h"30#include "errordialoghandler.h"
3131
32#include "trackinfoobject.h"
33
32#define THRESHOLD_REC 2. //high enough that its not triggered by white noise34#define THRESHOLD_REC 2. //high enough that its not triggered by white noise
3335
34class ControlLogpotmeter;36class ControlLogpotmeter;
@@ -49,8 +51,14 @@
49 void closeFile();51 void closeFile();
50 void updateFromPreferences();52 void updateFromPreferences();
51 bool fileOpen();53 bool fileOpen();
54 bool openCueFile();
55 void closeCueFile();
5256
53 private:57 private:
58 int getActiveTracks();
59 bool metaDataHasChanged();
60 void writeCueLine();
61
54 ConfigObject<ConfigValue> *m_config;62 ConfigObject<ConfigValue> *m_config;
55 Encoder *m_encoder;63 Encoder *m_encoder;
56 QByteArray m_OGGquality;64 QByteArray m_OGGquality;
@@ -62,6 +70,7 @@
62 QByteArray m_baAlbum;70 QByteArray m_baAlbum;
6371
64 QFile m_file;72 QFile m_file;
73 QFile m_cuefile;
65 QDataStream m_datastream;74 QDataStream m_datastream;
66 SNDFILE *m_sndfile;75 SNDFILE *m_sndfile;
67 SF_INFO m_sfInfo;76 SF_INFO m_sfInfo;
@@ -70,6 +79,16 @@
70 ControlObject* m_recReadyCO;79 ControlObject* m_recReadyCO;
7180
72 ControlObjectThread* m_samplerate;81 ControlObjectThread* m_samplerate;
82
83 int m_iMetaDataLife;
84 TrackPointer m_pCurrentTrack;
85 int m_iNumChannels;
86 double m_dLatency;
87
88 QByteArray m_cuefilename;
89 unsigned long m_cuesamplepos;
90 unsigned long m_cuetrack;
91 bool m_bCueIsEnabled;
73};92};
7493
75#endif94#endif