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