Merge lp:~neale/mixxx/usbbulk into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Neale Pickett
Status: Merged
Merged at revision: 3294
Proposed branch: lp:~neale/mixxx/usbbulk
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 1147 lines (+1017/-2)
14 files modified
mixxx/SConstruct (+1/-0)
mixxx/build/features.py (+31/-0)
mixxx/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp (+6/-1)
mixxx/plugins/soundsourcem4a/soundsourcem4a.cpp (+1/-1)
mixxx/res/controllers/Hercules DJ Control MP3 e2.cntrlr.xml (+15/-0)
mixxx/res/controllers/Hercules-mp3e2-compat.js (+178/-0)
mixxx/res/controllers/Hercules-mp3e2.js (+275/-0)
mixxx/res/controllers/common-bulk-midi.js (+5/-0)
mixxx/src/controllers/bulk/bulkcontroller.cpp (+274/-0)
mixxx/src/controllers/bulk/bulkcontroller.h (+105/-0)
mixxx/src/controllers/bulk/bulkenumerator.cpp (+71/-0)
mixxx/src/controllers/bulk/bulkenumerator.h (+24/-0)
mixxx/src/controllers/bulk/bulksupported.h (+24/-0)
mixxx/src/controllers/controllermanager.cpp (+7/-0)
To merge this branch: bzr merge lp:~neale/mixxx/usbbulk
Reviewer Review Type Date Requested Status
Mixxx Release Managers Pending
Review via email: mp+121312@code.launchpad.net

Description of the change

USB bulk driver, makes Hercules controllers work without a driver. Still has shutdown issues, but it works fine until you close mixxx. Linux users like me are in dire need of this.

Should be tested with non-Herc controllers to make sure the shutdown problem doesn't affect other USB devices.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'mixxx/SConstruct'
--- mixxx/SConstruct 2012-04-25 15:40:15 +0000
+++ mixxx/SConstruct 2012-08-25 14:32:20 +0000
@@ -38,6 +38,7 @@
38 features.MediaFoundation,38 features.MediaFoundation,
39 features.HSS1394,39 features.HSS1394,
40 features.HID,40 features.HID,
41 features.Bulk,
41 features.VinylControl,42 features.VinylControl,
42 features.Shoutcast,43 features.Shoutcast,
43 features.Profiling,44 features.Profiling,
4445
=== modified file 'mixxx/build/features.py'
--- mixxx/build/features.py 2012-07-08 07:30:32 +0000
+++ mixxx/build/features.py 2012-08-25 14:32:20 +0000
@@ -105,6 +105,37 @@
105 sources.append(os.path.join(self.HIDAPI_INTERNAL_PATH, 'mac/hid.c'))105 sources.append(os.path.join(self.HIDAPI_INTERNAL_PATH, 'mac/hid.c'))
106 return sources106 return sources
107107
108class Bulk(Feature):
109 def description(self):
110 return "USB Bulk controller support"
111
112 def enabled(self, build):
113 build.flags['bulk'] = util.get_flags(build.env, 'bulk', 1)
114 if int(build.flags['bulk']):
115 return True
116 return False
117
118 def add_options(self, build, vars):
119 vars.Add('bulk', 'Set to 1 to enable USB Bulk controller support.', 1)
120
121 def configure(self, build, conf):
122 if not self.enabled(build):
123 return
124
125 build.env.ParseConfig('pkg-config libusb-1.0 --silence-errors --cflags --libs')
126 if (not conf.CheckLib(['libusb-1.0', 'usb-1.0']) or
127 not conf.CheckHeader('libusb-1.0/libusb.h')):
128 raise Exception('Did not find the libusb 1.0 development library or its header file, exiting!')
129
130 build.env.Append(CPPDEFINES = '__BULK__')
131
132 def sources(self, build):
133 sources = ['controllers/bulk/bulkcontroller.cpp',
134 'controllers/bulk/bulkenumerator.cpp']
135
136 return sources
137
138
108class Mad(Feature):139class Mad(Feature):
109 def description(self):140 def description(self):
110 return "MAD MP3 Decoder"141 return "MAD MP3 Decoder"
111142
=== modified file 'mixxx/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp'
--- mixxx/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp 2012-05-04 03:44:44 +0000
+++ mixxx/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp 2012-08-25 14:32:20 +0000
@@ -145,7 +145,7 @@
145 priv->overflow_buf_len = 0;145 priv->overflow_buf_len = 0;
146 priv->overflow_buf = NULL;146 priv->overflow_buf = NULL;
147147
148 priv->sample_buf_len = 4096;148 priv->sample_buf_len = 8192;
149 priv->sample_buf = new char[priv->sample_buf_len];149 priv->sample_buf = new char[priv->sample_buf_len];
150 priv->sample_buf_frame = -1;150 priv->sample_buf_frame = -1;
151151
@@ -185,6 +185,8 @@
185 priv->aac_data_len = MP4GetTrackMaxSampleSize(priv->mp4.handle, priv->mp4.track);185 priv->aac_data_len = MP4GetTrackMaxSampleSize(priv->mp4.handle, priv->mp4.track);
186 priv->aac_data = new unsigned char[priv->aac_data_len];186 priv->aac_data = new unsigned char[priv->aac_data_len];
187187
188 qDebug() << "AAC Data Length:" << priv->aac_data_len;
189
188 priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track);190 priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track);
189 // MP4 frames are 1-indexed191 // MP4 frames are 1-indexed
190 priv->mp4.sample = 1;192 priv->mp4.sample = 1;
@@ -335,6 +337,7 @@
335 bytes = frame_info.samples * 2;337 bytes = frame_info.samples * 2;
336338
337 if (bytes > count) {339 if (bytes > count) {
340 qDebug() << "TOO MUCH BABY! " << bytes << ":" << count;
338 /* decoded too much; keep overflow. */341 /* decoded too much; keep overflow. */
339 //memcpy(priv->overflow_buf_base, sample_buf + count, bytes - count);342 //memcpy(priv->overflow_buf_base, sample_buf + count, bytes - count);
340 //priv->overflow_buf = priv->overflow_buf_base;343 //priv->overflow_buf = priv->overflow_buf_base;
@@ -358,6 +361,8 @@
358 if (priv->overflow_buf_len > 0) {361 if (priv->overflow_buf_len > 0) {
359 int len = priv->overflow_buf_len;362 int len = priv->overflow_buf_len;
360363
364 qDebug() << "using overflow " << len << ":" << count;
365
361 if (len > count)366 if (len > count)
362 len = count;367 len = count;
363368
364369
=== modified file 'mixxx/plugins/soundsourcem4a/soundsourcem4a.cpp'
--- mixxx/plugins/soundsourcem4a/soundsourcem4a.cpp 2011-03-31 16:07:22 +0000
+++ mixxx/plugins/soundsourcem4a/soundsourcem4a.cpp 2012-08-25 14:32:20 +0000
@@ -131,7 +131,7 @@
131131
132 int total_bytes_to_decode = size * m_iChannels;132 int total_bytes_to_decode = size * m_iChannels;
133 int total_bytes_decoded = 0;133 int total_bytes_decoded = 0;
134 int num_bytes_req = 4096;134 int num_bytes_req = 8192;
135 char* buffer = (char*)destination;135 char* buffer = (char*)destination;
136 SAMPLE * as_buffer = (SAMPLE*) destination; //pointer for mono->stereo filling.136 SAMPLE * as_buffer = (SAMPLE*) destination; //pointer for mono->stereo filling.
137 do {137 do {
138138
=== added file 'mixxx/res/controllers/Hercules DJ Control MP3 e2.cntrlr.xml'
--- mixxx/res/controllers/Hercules DJ Control MP3 e2.cntrlr.xml 1970-01-01 00:00:00 +0000
+++ mixxx/res/controllers/Hercules DJ Control MP3 e2.cntrlr.xml 2012-08-25 14:32:20 +0000
@@ -0,0 +1,15 @@
1<?xml version='1.0' encoding='utf-8'?>
2<MixxxControllerPreset mixxxVersion="1.11+" schemaVersion="1">
3 <info>
4 <name>Hercules DJ Control MP3 e2</name>
5 <author>Neale Pickett</author>
6 <description>Hercules DJ Control MP3 e2</description>
7 </info>
8 <controller id="DJ">
9 <scriptfiles>
10 <file functionprefix="" filename="common-bulk-midi.js"/>
11 <file functionprefix="" filename="Hercules DJ Control MP3 e2-scripts.js"/>
12 <file functionprefix="MP3e2" filename="Hercules-mp3e2-compat.js"/>
13 </scriptfiles>
14 </controller>
15</MixxxControllerPreset>
016
=== added file 'mixxx/res/controllers/Hercules-mp3e2-compat.js'
--- mixxx/res/controllers/Hercules-mp3e2-compat.js 1970-01-01 00:00:00 +0000
+++ mixxx/res/controllers/Hercules-mp3e2-compat.js 2012-08-25 14:32:20 +0000
@@ -0,0 +1,178 @@
1MP3e2 = Object()
2
3blink = new Object();
4
5function light(id, enable) {
6 if (enable == blink) {
7 bval = 0x7f;
8 val = 0;
9 } else {
10 bval = 0;
11 val = enable?0x7f:0;
12 }
13 midi.sendShortMsg(0x90, id, val);
14 midi.sendShortMsg(0x90, id + 0x30, bval);
15}
16
17function simpleButton(id) {
18 return function(val, group) {
19 var btn = (group == "[Channel1]")?(id):(id+20);
20
21 light(btn, !!val);
22 }
23}
24
25LEDs = [
26 ["loop_start_position", simpleButton(1)],
27 ["loop_end_position", simpleButton(2)],
28 ["loop_enabled", simpleButton(51)],
29 ["loop_enabled", simpleButton(52)],
30 ["hotcue_1_enabled", simpleButton(5)],
31 ["hotcue_2_enabled", simpleButton(6)],
32 ["hotcue_3_enabled", simpleButton(7)],
33 ["hotcue_4_enabled", simpleButton(8)],
34 ["flanger", simpleButton(67)],
35 ["play", simpleButton(15)],
36 ["pfl", simpleButton(16)],
37 ["beat_active", simpleButton(18)],
38];
39
40MP3e2.init = function(id) {
41 HerculesMP3e2.init(id);
42
43 // Set up LEDs
44 for (var i = 0; i < 2; i += 1) {
45 group = "[Channel" + (i+1) + "]";
46
47 for (var j in LEDs) {
48 var control = LEDs[j][0];
49 var func = LEDs[j][1];
50
51 engine.connectControl(group, control, func);
52 engine.trigger(group, control);
53 }
54 }
55}
56
57var c1 = '[Channel1]'
58var c2 = '[Channel2]'
59
60MP3e2.incomingData = function(data, length) {
61 for (var i = 0; i < length; i += 3) {
62 var status = data[i];
63 var midino = data[i+1];
64 var value = data[i+2];
65 var group;
66 var f = null;
67
68 if (status == 0xb0) {
69 if ((midino > 0x38) ||
70 ((midino < 0x34) && (midino & 1))) {
71 group = c2;
72 } else {
73 group = c1;
74 }
75 } else if (status == 0x90) {
76 if (midino <= 20) {
77 group = c1;
78 } else if (midino < 40) {
79 group = c2;
80 }
81 }
82
83 switch ((status<<8) | midino) {
84 case 0x9001: case 0x9015:
85 case 0x9002: case 0x9016:
86 case 0x9003: case 0x9017:
87 case 0x9004: case 0x9018:
88 case 0x9005: case 0x9019:
89 case 0x9006: case 0x901a:
90 case 0x9007: case 0x901b:
91 case 0x9008: case 0x901c:
92 f = HerculesMP3e2.keyButton;
93 break;
94 case 0x900a: case 0x901e:
95 case 0x900b: case 0x901f:
96 f = HerculesMP3e2.pitchbend;
97 break;
98 case 0x900c: case 0x9020:
99 f = "back";
100 break;
101 case 0x900d: case 0x9021:
102 f = "fwd";
103 break;
104 case 0x900e: case 0x9022:
105 f = HerculesMP3e2.cue;
106 break;
107 case 0x900f: case 0x9023:
108 if (value == 0) return;
109 f = "play";
110 value = ! engine.getValue(group, f);
111 break;
112 case 0x9010: case 0x9024:
113 if (value == 0) return;
114 f = "pfl";
115 value = ! engine.getValue(group, f);
116 break;
117 case 0x9011: case 0x9025:
118 f = HerculesMP3e2.loadTrack;
119 break;
120 case 0x9012: case 0x9026:
121 f = HerculesMP3e2.sync;
122 break;
123 case 0x9013: case 0x9027:
124 f = HerculesMP3e2.masterTempo;
125 break;
126
127
128 case 0x9029:
129 group = '[Playlist]';
130 f = 'SelectPrevTrack';
131 break;
132 case 0x902a:
133 group = '[Playlist]';
134 f = 'SelectNextTrack';
135 break;
136 case 0x902b:
137 case 0x902c:
138 group = '[Playlist]';
139 f = HerculesMP3e2.scroll;
140 break;
141 case 0x902d:
142 f = HerculesMP3e2.scratch;
143 break;
144 case 0x902e:
145 f = HerculesMP3e2.automix;
146 break;
147
148 case 0xb030: case 0xb031:
149 f = HerculesMP3e2.jogWheel;
150 break;
151 case 0xb032: case 0xb033:
152 f = HerculesMP3e2.pitch;
153 break;
154 case 0xb034: case 0xb039:
155 engine.setValue(group, "volume", script.absoluteLin(value, 0, 1));
156 break;
157 case 0xb035: case 0xb03a:
158 engine.setValue(group, "filterHigh", script.absoluteNonLin(value, 0, 1, 4));
159 break;
160 case 0xb036: case 0xb03b:
161 engine.setValue(group, "filterMid", script.absoluteNonLin(value, 0, 1, 4));
162 break;
163 case 0xb037: case 0xb03c:
164 engine.setValue(group, "filterLow", script.absoluteNonLin(value, 0, 1, 4));
165 break;
166 case 0xb038:
167 engine.setValue('[Master]', 'crossfader', script.absoluteLin(value, -1, 1));
168 break;
169 }
170
171 if (typeof(f) == 'string') {
172 engine.setValue(group, f, (value>0)?1:0);
173 } else if (f) {
174 f(0, midino, value, status, group);
175 }
176 }
177}
178
0179
=== added file 'mixxx/res/controllers/Hercules-mp3e2.js'
--- mixxx/res/controllers/Hercules-mp3e2.js 1970-01-01 00:00:00 +0000
+++ mixxx/res/controllers/Hercules-mp3e2.js 2012-08-25 14:32:20 +0000
@@ -0,0 +1,275 @@
1secondsBlink = 30;
2
3MP3e2 = new Object();
4
5function sendMsg(status, a, b) {
6 midi.sendShortMsg(status, a, b);
7 //controller.send([status, a, b], 3);
8}
9
10blink = new Object();
11
12function light(id, enable) {
13 if (enable == blink) {
14 bval = 0x7f;
15 val = 0;
16 } else {
17 bval = 0;
18 val = enable?0x7f:0;
19 }
20 sendMsg(0x90, id, val);
21 sendMsg(0x90, id + 0x30, bval);
22}
23
24function simpleButton(id) {
25 return function(val, group) {
26 var btn = (group == "[Channel1]")?(id):(id+20);
27
28 light(btn, !!val);
29 }
30}
31
32function toggleButton(id, val, group) {
33 var btn = (group == "[Channel1]")?(id):(id + 20);
34
35 light(btn, !!val);
36}
37
38function btn_loop_enabled(val, group) {
39 toggleButton(3, val, group);
40 toggleButton(4, val, group);
41}
42
43function btn_playposition(pos, group) {
44 var secondsToEnd = engine.getValue(group, "duration") * (1 - pos);
45 var btn = (group == "[Channel1]")?14:34;
46
47 if (secondsToEnd < 1) {
48 light(btn, false);
49 } else if (secondsToEnd < secondsBlink) {
50 light(btn, blink);
51 } else {
52 light(btn, true);
53 }
54}
55
56binds = [
57 ["loop_start_position", simpleButton(1)],
58 ["loop_end_position", simpleButton(2)],
59 ["hotcue_1_enabled", simpleButton(5)],
60 ["hotcue_2_enabled", simpleButton(6)],
61 ["hotcue_3_enabled", simpleButton(7)],
62 ["hotcue_4_enabled", simpleButton(8)],
63 ["play", simpleButton(15)],
64 ["pfl", simpleButton(16)],
65 ["playposition", btn_playposition],
66 ["loop_enabled", btn_loop_enabled],
67];
68
69meta = false;
70ctl_id = "";
71
72MP3e2.init = function(id) {
73 print("MP3e2 controller initialized: " + id);
74
75 ctl_id = id;
76
77 for (var i = 0; i < 2; i += 1) {
78 group = "[Channel" + (i+1) + "]";
79
80
81 for (var j in binds) {
82 var control = binds[j][0];
83 var func = binds[j][1];
84
85 engine.connectControl(group, control, func);
86 engine.trigger(group, control);
87 }
88 }
89
90 // Turn everything off
91 for (i = 1; i < 46; i += 1) {
92 light(i, false);
93 }
94
95 // Request all faders report position
96 sendMsg(0xb0, 0x7f, 0x7f);
97
98 // Turn on some lights
99 light(46, true); // Automix
100 //light(14, true); // Cue A
101 //light(34, true); // Cue B
102}
103
104MP3e2.shutdown = function() {
105 controller.close();
106 print("MP3e2 controller shutdown: " + ctl_id);
107}
108
109meta_buttons = {
110 1: "loop_start_position",
111 2: "loop_end_position",
112 5: "hotcue_1_clear",
113 6: "hotcue_2_clear",
114 7: "hotcue_3_clear",
115 8: "hotcue_4_clear",
116};
117
118buttons = {
119 1: "loop_in",
120 2: "loop_out",
121 3: "reloop_exit",
122 4: "reloop_exit",
123 5: "hotcue_1_activate",
124 6: "hotcue_2_activate",
125 7: "hotcue_3_activate",
126 8: "hotcue_4_activate",
127 10: "rate_temp_down",
128 11: "rate_temp_up",
129 12: "back",
130 13: "fwd",
131 14: "cue_default",
132 15: "play",
133 16: "pfl",
134 17: "LoadSelectedTrack",
135 18: "beatsync",
136
137 41: "SelectPrevTrack",
138 42: "SelectNextTrack",
139};
140
141function handleButton(id, pressed) {
142 var val = pressed ? 1 : 0;
143 var did = id;
144 var group, ctl;
145
146 if (id > 40) {
147 group = '[Playlist]';
148 } else if (id > 20) {
149 group = '[Channel2]';
150 did -= 20;
151 } else {
152 group = '[Channel1]';
153 }
154
155 if (meta) {
156 ctl = meta_buttons[did];
157 }
158 if (! ctl) {
159 ctl = buttons[did];
160 }
161
162 print(id + ": " + ctl);
163 if (ctl) {
164 switch (did) {
165 case 1:
166 case 2:
167 case 5:
168 case 6:
169 case 7:
170 case 8:
171 if (meta) {
172 val = -pressed;
173 }
174 break;
175 case 15:
176 case 16:
177 // Ignore button release, toggle value
178 if (! pressed) {
179 return;
180 }
181 val = ! engine.getValue(group, ctl);
182 break;
183 }
184
185 engine.setValue(group, ctl, val);
186 return;
187 }
188
189 switch (id) {
190 case 46:
191 meta = !!pressed;
192 light(30, meta);
193 light(31, meta);
194 light(10, meta);
195 light(11, meta);
196 light(19, meta);
197 light(39, meta);
198 break;
199 }
200}
201
202
203faders = {
204 0x35: "filterHigh",
205 0x36: "filterMid",
206 0x37: "filterLow"
207};
208
209function handleFader(id, val) {
210 if (id < 0x34) {
211 group = '[Channel' + ((id & 1) + 1) + ']';
212 id = id & 0xfe;
213 } else if (id < 0x39) {
214 group = '[Channel1]';
215 } else {
216 group = '[Channel2]';
217 id -= 5;
218 }
219
220 ctl = faders[id];
221
222 switch (id) {
223 case 0x30: // Jog wheel
224 if (val == 127) {
225 dir = -1;
226 } else {
227 dir = 1;
228 }
229 engine.setValue(group, "jog", dir);
230 break;
231 case 0x32: // Pitch adjust
232 if (val == 127) {
233 ctl = "rate_perm_down";
234 } else {
235 ctl = "rate_perm_up";
236 }
237 engine.setValue(group, ctl, 1);
238 engine.setValue(group, ctl, 0);
239 break;
240 case 0x34:
241 fval = script.absoluteLin(val, 0, 1);
242 engine.setValue(group, "volume", fval);
243 return;
244 case 0x35:
245 case 0x36:
246 case 0x37:
247 fval = script.absoluteNonLin(val, 0, 1, 4);
248 engine.setValue(group, ctl, fval);
249 break;
250 case 0x38: // Crossfader
251 fval = script.absoluteLin(val, -1, 1);
252 engine.setValue('[Master]', 'crossfader', fval);
253 break;
254 }
255}
256
257MP3e2.incomingData = function(data, length) {
258 for (i = 0; i < length; i += 3) {
259 switch (data[i]) {
260 case 0x90:
261 handleButton(data[i+1], data[i+2] == 0x7f);
262 break;
263 case 0xb0:
264 handleFader(data[i+1], data[i+2]);
265 break;
266 default:
267 print("Unhandled packet: " +
268 " 0x" + data[i].toString(16) +
269 " 0x" + data[i+1].toString(16) +
270 " 0x" + data[i+2].teString(16))
271 break;
272 }
273 }
274}
275
0276
=== added file 'mixxx/res/controllers/common-bulk-midi.js'
--- mixxx/res/controllers/common-bulk-midi.js 1970-01-01 00:00:00 +0000
+++ mixxx/res/controllers/common-bulk-midi.js 2012-08-25 14:32:20 +0000
@@ -0,0 +1,5 @@
1midi = new Object();
2
3midi.sendShortMsg = function(status, a, b) {
4 controller.send([status, a, b], 3);
5}
06
=== added directory 'mixxx/src/controllers/bulk'
=== added file 'mixxx/src/controllers/bulk/bulkcontroller.cpp'
--- mixxx/src/controllers/bulk/bulkcontroller.cpp 1970-01-01 00:00:00 +0000
+++ mixxx/src/controllers/bulk/bulkcontroller.cpp 2012-08-25 14:32:20 +0000
@@ -0,0 +1,274 @@
1/**
2 * @file bulkcontroller.cpp
3 * @author Neale Pickett neale@woozle.org
4 * @date Thu Jun 28 2012
5 * @brief USB Bulk controller backend
6 *
7 */
8
9#include "controllers/bulk/bulkcontroller.h"
10#include "controllers/bulk/bulksupported.h"
11
12#include <time.h>
13#include <stdio.h>
14
15
16/* This is for my debugging, because I like typing
17 DUMP();
18 more than typing
19 qDebug() << "foo";
20 */
21#ifdef NODUMP
22# define DUMPf(fmt, args...)
23#else
24# define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
25#endif
26#define DUMP() DUMPf("")
27#define DUMP_d(v) DUMPf("%s = %d", #v, v)
28#define DUMP_x(v) DUMPf("%s = 0x%x", #v, v)
29#define DUMP_s(v) DUMPf("%s = %s", #v, v)
30#define DUMP_c(v) DUMPf("%s = '%c' (0x%02x)", #v, v, v)
31#define DUMP_p(v) DUMPf("%s = %p", #v, v)
32
33BulkReader::BulkReader(libusb_device_handle *handle, unsigned char in_epaddr)
34 : QThread() {
35 m_phandle = handle;
36 m_in_epaddr = in_epaddr;
37}
38
39BulkReader::~BulkReader() {
40 m_phandle = NULL;
41}
42
43void BulkReader::stop() {
44 m_stop = 1;
45}
46
47void BulkReader::run() {
48 m_stop = 0;
49 unsigned char data[255];
50
51 while (m_stop == 0) {
52 // Blocked polling: The only problem with this is that we can't close
53 // the device until the block is released, which means the controller
54 // has to send more data
55 //result = hid_read_timeout(m_pHidDevice, data, 255, -1);
56
57 // This relieves that at the cost of higher CPU usage since we only
58 // block for a short while (500ms)
59 int transferred;
60 int result;
61
62 result = libusb_bulk_transfer(m_phandle,
63 m_in_epaddr,
64 data, sizeof data,
65 &transferred, 500);
66 if (result >= 0) {
67 //qDebug() << "Read" << result << "bytes, pointer:" << data;
68 QByteArray outData((char *)data, transferred);
69 emit(incomingData(outData));
70 }
71
72 }
73 qDebug() << "Stopped Reader";
74}
75
76static QString
77get_string(libusb_device_handle *handle, u_int8_t id)
78{
79 unsigned char buf[128];
80 QString s;
81
82 buf[0] = 0;
83 if (id) {
84 (void)libusb_get_string_descriptor_ascii(handle, id, buf, sizeof buf);
85 }
86
87 return QString::fromAscii((char *)buf);
88}
89
90
91BulkController::BulkController(libusb_device_handle *handle,
92 struct libusb_device_descriptor *desc)
93{
94 vendor_id = desc->idVendor;
95 product_id = desc->idProduct;
96
97 manufacturer = get_string(handle, desc->iManufacturer);
98 product = get_string(handle, desc->iProduct);
99 m_sUID = get_string(handle, desc->iSerialNumber);
100
101 setDeviceCategory(tr("USB Controller"));
102
103 setDeviceName(QString("%1 %2").arg(product).arg(m_sUID));
104
105 setInputDevice(true);
106 setOutputDevice(true);
107 m_pReader = NULL;
108}
109
110BulkController::~BulkController() {
111 close();
112}
113
114void BulkController::visit(const MidiControllerPreset* preset) {
115 Q_UNUSED(preset);
116 // TODO(XXX): throw a hissy fit.
117 qDebug() << "ERROR: Attempting to load a MidiControllerPreset to an HidController!";
118}
119
120void BulkController::visit(const HidControllerPreset* preset) {
121 m_preset = *preset;
122 // Emit presetLoaded with a clone of the preset.
123 emit(presetLoaded(getPreset()));
124}
125
126bool BulkController::savePreset(const QString fileName) const {
127 HidControllerPresetFileHandler handler;
128 return handler.save(m_preset, getName(), fileName);
129}
130
131bool BulkController::matchPreset(const PresetInfo& preset) {
132 const QList< QHash<QString,QString> > products = preset.getProducts();
133 QHash <QString, QString> product;
134 foreach (product, products) {
135 if (matchProductInfo(product))
136 return true;
137 }
138 return false;
139}
140
141bool BulkController::matchProductInfo(QHash <QString,QString > info) {
142 int value;
143 bool ok;
144 // Product and vendor match is always required
145 value = info["vendor_id"].toInt(&ok,16);
146 if (!ok || vendor_id!=value) return false;
147 value = info["product_id"].toInt(&ok,16);
148 if (!ok || product_id!=value) return false;
149
150 // Match found
151 return true;
152}
153
154int BulkController::open() {
155 int i;
156
157 if (isOpen()) {
158 qDebug() << "USB Bulk device" << getName() << "already open";
159 return -1;
160 }
161
162 /* Look up endpoint addresses in supported database */
163 for (i = 0; bulk_supported[i].vendor_id; i += 1) {
164 if ((bulk_supported[i].vendor_id == vendor_id) &&
165 (bulk_supported[i].product_id == product_id)) {
166 in_epaddr = bulk_supported[i].in_epaddr;
167 out_epaddr = bulk_supported[i].out_epaddr;
168 break;
169 }
170 }
171
172 if (bulk_supported[i].vendor_id == 0) {
173 qWarning() << "USB Bulk device" << getName() << "unsupported";
174 return -1;
175 }
176 m_phandle = NULL;
177
178 // XXX: we should enumerate devices and match vendor, product, and serial
179 if (m_phandle == NULL) {
180 m_phandle = libusb_open_device_with_vid_pid(NULL, vendor_id, product_id);
181 }
182
183 if (m_phandle == NULL) {
184 qWarning() << "Unable to open USB Bulk device" << getName();
185 return -1;
186 }
187
188 setOpen(true);
189 startEngine();
190
191 if (m_pReader != NULL) {
192 qWarning() << "BulkReader already present for" << getName();
193 } else {
194 m_pReader = new BulkReader(m_phandle, in_epaddr);
195 m_pReader->setObjectName(QString("BulkReader %1").arg(getName()));
196
197 connect(m_pReader, SIGNAL(incomingData(QByteArray)),
198 this, SLOT(receive(QByteArray)));
199
200 // Controller input needs to be prioritized since it can affect the
201 // audio directly, like when scratching
202 m_pReader->start(QThread::HighPriority);
203 }
204
205 return 0;
206}
207
208int BulkController::close() {
209 if (!isOpen()) {
210 qDebug() << " device" << getName() << "already closed";
211 return -1;
212 }
213
214 qDebug() << "Shutting down USB Bulk device" << getName();
215
216 // Stop the reading thread
217 if (m_pReader == NULL) {
218 qWarning() << "BulkReader not present for" << getName()
219 << "yet the device is open!";
220 } else {
221 disconnect(m_pReader, SIGNAL(incomingData(QByteArray)),
222 this, SLOT(receive(QByteArray)));
223 m_pReader->stop();
224 if (debugging()) qDebug() << " Waiting on reader to finish";
225 m_pReader->wait();
226 delete m_pReader;
227 m_pReader = NULL;
228 }
229
230 // Stop controller engine here to ensure it's done before the device is closed
231 // incase it has any final parting messages
232 stopEngine();
233
234 // Close device
235 if (debugging()) {
236 qDebug() << " Closing device";
237 }
238 libusb_close(m_phandle);
239 setOpen(false);
240 return 0;
241}
242
243void BulkController::send(QList<int> data, unsigned int length) {
244 Q_UNUSED(length);
245 QByteArray temp;
246
247 foreach (int datum, data) {
248 temp.append(datum);
249 }
250 send(temp);
251}
252
253void BulkController::send(QByteArray data) {
254 int ret;
255 int transferred;
256
257 // XXX: don't get drunk again.
258 ret = libusb_bulk_transfer(m_phandle, out_epaddr,
259 (unsigned char *)data.constData(), data.size(),
260 &transferred, 0);
261 if (ret < 0) {
262 if (debugging()) {
263 qWarning() << "Unable to send data to" << getName()
264 << "serial #" << m_sUID << ":"
265 << QString::fromAscii(libusb_error_name(ret));
266 } else {
267 qWarning() << "Unable to send data to" << getName() << ":"
268 << QString::fromAscii(libusb_error_name(ret));
269 }
270 } else if (debugging()) {
271 qDebug() << ret << "bytes sent to" << getName()
272 << "serial #" << m_sUID;
273 }
274}
0275
=== added file 'mixxx/src/controllers/bulk/bulkcontroller.h'
--- mixxx/src/controllers/bulk/bulkcontroller.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/controllers/bulk/bulkcontroller.h 2012-08-25 14:32:20 +0000
@@ -0,0 +1,105 @@
1/**
2 * @file bulkcontroller.h
3 * @author Neale Picket neale@woozle.org
4 * @date Thu Jun 28 2012
5 * @brief USB Bulk controller backend
6 */
7
8#ifndef BULKCONTROLLER_H
9#define BULKCONTROLLER_H
10
11#include <libusb.h>
12
13#include <QAtomicInt>
14
15#include "controllers/controller.h"
16#include "controllers/hid/hidcontrollerpreset.h"
17#include "controllers/hid/hidcontrollerpresetfilehandler.h"
18
19class BulkReader : public QThread {
20 Q_OBJECT
21 public:
22 BulkReader(libusb_device_handle *handle, unsigned char in_epaddr);
23 virtual ~BulkReader();
24
25 void stop();
26
27 signals:
28 void incomingData(QByteArray data);
29
30 protected:
31 void run();
32
33 private:
34 libusb_device_handle *m_phandle;
35 QAtomicInt m_stop;
36 unsigned char m_in_epaddr;
37};
38
39class BulkController : public Controller {
40 Q_OBJECT
41 public:
42 BulkController(libusb_device_handle *handle, struct libusb_device_descriptor *desc);
43 virtual ~BulkController();
44
45 virtual ControllerPresetPointer getPreset() const {
46 HidControllerPreset* pClone = new HidControllerPreset();
47 *pClone = m_preset;
48 return ControllerPresetPointer(pClone);
49 }
50
51 virtual bool savePreset(const QString fileName) const;
52
53 virtual ControllerPresetFileHandler* getFileHandler() const {
54 return new HidControllerPresetFileHandler();
55 }
56
57 virtual void visit(const MidiControllerPreset* preset);
58 virtual void visit(const HidControllerPreset* preset);
59
60 virtual bool isMappable() const {
61 return m_preset.isMappable();
62 }
63
64 virtual bool matchPreset(const PresetInfo& preset);
65 virtual bool matchProductInfo(QHash <QString,QString >);
66
67 protected:
68 Q_INVOKABLE void send(QList<int> data, unsigned int length);
69
70 private slots:
71 int open();
72 int close();
73
74 private:
75 // For devices which only support a single report, reportID must be set to
76 // 0x0.
77 virtual void send(QByteArray data);
78
79 virtual bool isPolling() const {
80 return false;
81 }
82
83 // Returns a pointer to the currently loaded controller preset. For internal
84 // use only.
85 virtual ControllerPreset* preset() {
86 return &m_preset;
87 }
88
89 libusb_device_handle *m_phandle;
90
91 // Local copies of things we need from desc
92
93 unsigned short vendor_id;
94 unsigned short product_id;
95 unsigned char in_epaddr;
96 unsigned char out_epaddr;
97 QString manufacturer;
98 QString product;
99
100 QString m_sUID;
101 BulkReader* m_pReader;
102 HidControllerPreset m_preset;
103};
104
105#endif
0106
=== added file 'mixxx/src/controllers/bulk/bulkenumerator.cpp'
--- mixxx/src/controllers/bulk/bulkenumerator.cpp 1970-01-01 00:00:00 +0000
+++ mixxx/src/controllers/bulk/bulkenumerator.cpp 2012-08-25 14:32:20 +0000
@@ -0,0 +1,71 @@
1/**
2 * @file bulkenumerator.cpp
3 * @author Neale Picket neale@woozle.org
4 * @date Thu Jun 28 2012
5 * @brief USB Bulk controller backend
6 */
7
8#include <libusb.h>
9
10#include "controllers/bulk/bulkcontroller.h"
11#include "controllers/bulk/bulkenumerator.h"
12#include "controllers/bulk/bulksupported.h"
13
14BulkEnumerator::BulkEnumerator() : ControllerEnumerator() {
15 libusb_init(NULL);
16}
17
18BulkEnumerator::~BulkEnumerator() {
19 qDebug() << "Deleting USB Bulk devices...";
20 while (m_devices.size() > 0) {
21 delete m_devices.takeLast();
22 }
23 libusb_exit(NULL);
24}
25
26static int
27is_interesting(struct libusb_device_descriptor *desc)
28{
29 int i;
30
31 for (i = 0; bulk_supported[i].vendor_id; i += 1) {
32 if ((bulk_supported[i].vendor_id == desc->idVendor) &&
33 (bulk_supported[i].product_id == desc->idProduct)) {
34 return 1;
35 }
36 }
37
38 return 0;
39}
40
41QList<Controller*> BulkEnumerator::queryDevices() {
42 qDebug() << "Scanning USB Bulk devices:";
43
44 libusb_device **list;
45 ssize_t cnt = libusb_get_device_list(NULL, &list);
46 ssize_t i = 0;
47 int err = 0;
48
49 for (i = 0; i < cnt; i++) {
50 libusb_device *device = list[i];
51 struct libusb_device_descriptor desc;
52
53 libusb_get_device_descriptor(device, &desc);
54 if (is_interesting(&desc)) {
55 struct libusb_device_handle *handle;
56
57 err = libusb_open(device, &handle);
58 if (err) {
59 qDebug() << "Error opening a device";
60 continue;
61 }
62
63 BulkController* currentDevice = new BulkController(handle, &desc);
64 m_devices.push_back(currentDevice);
65 }
66 }
67
68 libusb_free_device_list(list, 1);
69
70 return m_devices;
71}
072
=== added file 'mixxx/src/controllers/bulk/bulkenumerator.h'
--- mixxx/src/controllers/bulk/bulkenumerator.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/controllers/bulk/bulkenumerator.h 2012-08-25 14:32:20 +0000
@@ -0,0 +1,24 @@
1/**
2* @file bulkenumerator.h
3* @author Neale Pickett neale@woozle.org
4* @date Thu Jun 28 2012
5* @brief Locate supported USB bulk controllers
6*/
7
8#ifndef BULKENUMERATOR_H
9#define BULKENUMERATOR_H
10
11#include "controllers/controllerenumerator.h"
12
13class BulkEnumerator : public ControllerEnumerator {
14 public:
15 BulkEnumerator();
16 virtual ~BulkEnumerator();
17
18 QList<Controller*> queryDevices();
19
20 private:
21 QList<Controller*> m_devices;
22};
23
24#endif
025
=== added file 'mixxx/src/controllers/bulk/bulksupported.h'
--- mixxx/src/controllers/bulk/bulksupported.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/controllers/bulk/bulksupported.h 2012-08-25 14:32:20 +0000
@@ -0,0 +1,24 @@
1/**
2* @file bulksupported.h
3* @author Neale Picket neale@woozle.org
4* @date Thu Jun 28 2012
5* @brief A list of supported USB bulk devices
6*/
7
8#ifndef BULKSUPPORTED_H
9#define BULKSUPPORTED_H
10
11typedef struct bulk_supported {
12 unsigned short vendor_id;
13 unsigned short product_id;
14 unsigned char in_epaddr;
15 unsigned char out_epaddr;
16} bulk_supported_t;
17
18static bulk_supported_t bulk_supported[] = {
19 {0x06f8, 0xb105, 0x82, 0x03}, // Hercules MP3e2
20 {0x06f8, 0xb100, 0x86, 0x06}, // Hercules Mk2
21 {0, 0, 0, 0}
22};
23
24#endif
025
=== modified file 'mixxx/src/controllers/controllermanager.cpp'
--- mixxx/src/controllers/controllermanager.cpp 2012-07-08 23:27:47 +0000
+++ mixxx/src/controllers/controllermanager.cpp 2012-08-25 14:32:20 +0000
@@ -21,6 +21,10 @@
21 #include "controllers/hid/hidenumerator.h"21 #include "controllers/hid/hidenumerator.h"
22#endif22#endif
2323
24#ifdef __BULK__
25# include "controllers/bulk/bulkenumerator.h"
26#endif
27
24// http://developer.qt.nokia.com/wiki/Threads_Events_QObjects28// http://developer.qt.nokia.com/wiki/Threads_Events_QObjects
2529
26// Poll every 1ms (where possible) for good controller response30// Poll every 1ms (where possible) for good controller response
@@ -77,6 +81,9 @@
77#ifdef __HSS1394__81#ifdef __HSS1394__
78 m_enumerators.append(new Hss1394Enumerator());82 m_enumerators.append(new Hss1394Enumerator());
79#endif83#endif
84#ifdef __BULK__
85 m_enumerators.append(new BulkEnumerator());
86#endif
80#ifdef __HID__87#ifdef __HID__
81 m_enumerators.append(new HidEnumerator());88 m_enumerators.append(new HidEnumerator());
82#endif89#endif