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
1=== modified file 'mixxx/SConstruct'
2--- mixxx/SConstruct 2012-04-25 15:40:15 +0000
3+++ mixxx/SConstruct 2012-08-25 14:32:20 +0000
4@@ -38,6 +38,7 @@
5 features.MediaFoundation,
6 features.HSS1394,
7 features.HID,
8+ features.Bulk,
9 features.VinylControl,
10 features.Shoutcast,
11 features.Profiling,
12
13=== modified file 'mixxx/build/features.py'
14--- mixxx/build/features.py 2012-07-08 07:30:32 +0000
15+++ mixxx/build/features.py 2012-08-25 14:32:20 +0000
16@@ -105,6 +105,37 @@
17 sources.append(os.path.join(self.HIDAPI_INTERNAL_PATH, 'mac/hid.c'))
18 return sources
19
20+class Bulk(Feature):
21+ def description(self):
22+ return "USB Bulk controller support"
23+
24+ def enabled(self, build):
25+ build.flags['bulk'] = util.get_flags(build.env, 'bulk', 1)
26+ if int(build.flags['bulk']):
27+ return True
28+ return False
29+
30+ def add_options(self, build, vars):
31+ vars.Add('bulk', 'Set to 1 to enable USB Bulk controller support.', 1)
32+
33+ def configure(self, build, conf):
34+ if not self.enabled(build):
35+ return
36+
37+ build.env.ParseConfig('pkg-config libusb-1.0 --silence-errors --cflags --libs')
38+ if (not conf.CheckLib(['libusb-1.0', 'usb-1.0']) or
39+ not conf.CheckHeader('libusb-1.0/libusb.h')):
40+ raise Exception('Did not find the libusb 1.0 development library or its header file, exiting!')
41+
42+ build.env.Append(CPPDEFINES = '__BULK__')
43+
44+ def sources(self, build):
45+ sources = ['controllers/bulk/bulkcontroller.cpp',
46+ 'controllers/bulk/bulkenumerator.cpp']
47+
48+ return sources
49+
50+
51 class Mad(Feature):
52 def description(self):
53 return "MAD MP3 Decoder"
54
55=== modified file 'mixxx/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp'
56--- mixxx/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp 2012-05-04 03:44:44 +0000
57+++ mixxx/plugins/soundsourcem4a/m4a/mp4-mixxx.cpp 2012-08-25 14:32:20 +0000
58@@ -145,7 +145,7 @@
59 priv->overflow_buf_len = 0;
60 priv->overflow_buf = NULL;
61
62- priv->sample_buf_len = 4096;
63+ priv->sample_buf_len = 8192;
64 priv->sample_buf = new char[priv->sample_buf_len];
65 priv->sample_buf_frame = -1;
66
67@@ -185,6 +185,8 @@
68 priv->aac_data_len = MP4GetTrackMaxSampleSize(priv->mp4.handle, priv->mp4.track);
69 priv->aac_data = new unsigned char[priv->aac_data_len];
70
71+ qDebug() << "AAC Data Length:" << priv->aac_data_len;
72+
73 priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track);
74 // MP4 frames are 1-indexed
75 priv->mp4.sample = 1;
76@@ -335,6 +337,7 @@
77 bytes = frame_info.samples * 2;
78
79 if (bytes > count) {
80+ qDebug() << "TOO MUCH BABY! " << bytes << ":" << count;
81 /* decoded too much; keep overflow. */
82 //memcpy(priv->overflow_buf_base, sample_buf + count, bytes - count);
83 //priv->overflow_buf = priv->overflow_buf_base;
84@@ -358,6 +361,8 @@
85 if (priv->overflow_buf_len > 0) {
86 int len = priv->overflow_buf_len;
87
88+ qDebug() << "using overflow " << len << ":" << count;
89+
90 if (len > count)
91 len = count;
92
93
94=== modified file 'mixxx/plugins/soundsourcem4a/soundsourcem4a.cpp'
95--- mixxx/plugins/soundsourcem4a/soundsourcem4a.cpp 2011-03-31 16:07:22 +0000
96+++ mixxx/plugins/soundsourcem4a/soundsourcem4a.cpp 2012-08-25 14:32:20 +0000
97@@ -131,7 +131,7 @@
98
99 int total_bytes_to_decode = size * m_iChannels;
100 int total_bytes_decoded = 0;
101- int num_bytes_req = 4096;
102+ int num_bytes_req = 8192;
103 char* buffer = (char*)destination;
104 SAMPLE * as_buffer = (SAMPLE*) destination; //pointer for mono->stereo filling.
105 do {
106
107=== added file 'mixxx/res/controllers/Hercules DJ Control MP3 e2.cntrlr.xml'
108--- mixxx/res/controllers/Hercules DJ Control MP3 e2.cntrlr.xml 1970-01-01 00:00:00 +0000
109+++ mixxx/res/controllers/Hercules DJ Control MP3 e2.cntrlr.xml 2012-08-25 14:32:20 +0000
110@@ -0,0 +1,15 @@
111+<?xml version='1.0' encoding='utf-8'?>
112+<MixxxControllerPreset mixxxVersion="1.11+" schemaVersion="1">
113+ <info>
114+ <name>Hercules DJ Control MP3 e2</name>
115+ <author>Neale Pickett</author>
116+ <description>Hercules DJ Control MP3 e2</description>
117+ </info>
118+ <controller id="DJ">
119+ <scriptfiles>
120+ <file functionprefix="" filename="common-bulk-midi.js"/>
121+ <file functionprefix="" filename="Hercules DJ Control MP3 e2-scripts.js"/>
122+ <file functionprefix="MP3e2" filename="Hercules-mp3e2-compat.js"/>
123+ </scriptfiles>
124+ </controller>
125+</MixxxControllerPreset>
126
127=== added file 'mixxx/res/controllers/Hercules-mp3e2-compat.js'
128--- mixxx/res/controllers/Hercules-mp3e2-compat.js 1970-01-01 00:00:00 +0000
129+++ mixxx/res/controllers/Hercules-mp3e2-compat.js 2012-08-25 14:32:20 +0000
130@@ -0,0 +1,178 @@
131+MP3e2 = Object()
132+
133+blink = new Object();
134+
135+function light(id, enable) {
136+ if (enable == blink) {
137+ bval = 0x7f;
138+ val = 0;
139+ } else {
140+ bval = 0;
141+ val = enable?0x7f:0;
142+ }
143+ midi.sendShortMsg(0x90, id, val);
144+ midi.sendShortMsg(0x90, id + 0x30, bval);
145+}
146+
147+function simpleButton(id) {
148+ return function(val, group) {
149+ var btn = (group == "[Channel1]")?(id):(id+20);
150+
151+ light(btn, !!val);
152+ }
153+}
154+
155+LEDs = [
156+ ["loop_start_position", simpleButton(1)],
157+ ["loop_end_position", simpleButton(2)],
158+ ["loop_enabled", simpleButton(51)],
159+ ["loop_enabled", simpleButton(52)],
160+ ["hotcue_1_enabled", simpleButton(5)],
161+ ["hotcue_2_enabled", simpleButton(6)],
162+ ["hotcue_3_enabled", simpleButton(7)],
163+ ["hotcue_4_enabled", simpleButton(8)],
164+ ["flanger", simpleButton(67)],
165+ ["play", simpleButton(15)],
166+ ["pfl", simpleButton(16)],
167+ ["beat_active", simpleButton(18)],
168+];
169+
170+MP3e2.init = function(id) {
171+ HerculesMP3e2.init(id);
172+
173+ // Set up LEDs
174+ for (var i = 0; i < 2; i += 1) {
175+ group = "[Channel" + (i+1) + "]";
176+
177+ for (var j in LEDs) {
178+ var control = LEDs[j][0];
179+ var func = LEDs[j][1];
180+
181+ engine.connectControl(group, control, func);
182+ engine.trigger(group, control);
183+ }
184+ }
185+}
186+
187+var c1 = '[Channel1]'
188+var c2 = '[Channel2]'
189+
190+MP3e2.incomingData = function(data, length) {
191+ for (var i = 0; i < length; i += 3) {
192+ var status = data[i];
193+ var midino = data[i+1];
194+ var value = data[i+2];
195+ var group;
196+ var f = null;
197+
198+ if (status == 0xb0) {
199+ if ((midino > 0x38) ||
200+ ((midino < 0x34) && (midino & 1))) {
201+ group = c2;
202+ } else {
203+ group = c1;
204+ }
205+ } else if (status == 0x90) {
206+ if (midino <= 20) {
207+ group = c1;
208+ } else if (midino < 40) {
209+ group = c2;
210+ }
211+ }
212+
213+ switch ((status<<8) | midino) {
214+ case 0x9001: case 0x9015:
215+ case 0x9002: case 0x9016:
216+ case 0x9003: case 0x9017:
217+ case 0x9004: case 0x9018:
218+ case 0x9005: case 0x9019:
219+ case 0x9006: case 0x901a:
220+ case 0x9007: case 0x901b:
221+ case 0x9008: case 0x901c:
222+ f = HerculesMP3e2.keyButton;
223+ break;
224+ case 0x900a: case 0x901e:
225+ case 0x900b: case 0x901f:
226+ f = HerculesMP3e2.pitchbend;
227+ break;
228+ case 0x900c: case 0x9020:
229+ f = "back";
230+ break;
231+ case 0x900d: case 0x9021:
232+ f = "fwd";
233+ break;
234+ case 0x900e: case 0x9022:
235+ f = HerculesMP3e2.cue;
236+ break;
237+ case 0x900f: case 0x9023:
238+ if (value == 0) return;
239+ f = "play";
240+ value = ! engine.getValue(group, f);
241+ break;
242+ case 0x9010: case 0x9024:
243+ if (value == 0) return;
244+ f = "pfl";
245+ value = ! engine.getValue(group, f);
246+ break;
247+ case 0x9011: case 0x9025:
248+ f = HerculesMP3e2.loadTrack;
249+ break;
250+ case 0x9012: case 0x9026:
251+ f = HerculesMP3e2.sync;
252+ break;
253+ case 0x9013: case 0x9027:
254+ f = HerculesMP3e2.masterTempo;
255+ break;
256+
257+
258+ case 0x9029:
259+ group = '[Playlist]';
260+ f = 'SelectPrevTrack';
261+ break;
262+ case 0x902a:
263+ group = '[Playlist]';
264+ f = 'SelectNextTrack';
265+ break;
266+ case 0x902b:
267+ case 0x902c:
268+ group = '[Playlist]';
269+ f = HerculesMP3e2.scroll;
270+ break;
271+ case 0x902d:
272+ f = HerculesMP3e2.scratch;
273+ break;
274+ case 0x902e:
275+ f = HerculesMP3e2.automix;
276+ break;
277+
278+ case 0xb030: case 0xb031:
279+ f = HerculesMP3e2.jogWheel;
280+ break;
281+ case 0xb032: case 0xb033:
282+ f = HerculesMP3e2.pitch;
283+ break;
284+ case 0xb034: case 0xb039:
285+ engine.setValue(group, "volume", script.absoluteLin(value, 0, 1));
286+ break;
287+ case 0xb035: case 0xb03a:
288+ engine.setValue(group, "filterHigh", script.absoluteNonLin(value, 0, 1, 4));
289+ break;
290+ case 0xb036: case 0xb03b:
291+ engine.setValue(group, "filterMid", script.absoluteNonLin(value, 0, 1, 4));
292+ break;
293+ case 0xb037: case 0xb03c:
294+ engine.setValue(group, "filterLow", script.absoluteNonLin(value, 0, 1, 4));
295+ break;
296+ case 0xb038:
297+ engine.setValue('[Master]', 'crossfader', script.absoluteLin(value, -1, 1));
298+ break;
299+ }
300+
301+ if (typeof(f) == 'string') {
302+ engine.setValue(group, f, (value>0)?1:0);
303+ } else if (f) {
304+ f(0, midino, value, status, group);
305+ }
306+ }
307+}
308+
309
310=== added file 'mixxx/res/controllers/Hercules-mp3e2.js'
311--- mixxx/res/controllers/Hercules-mp3e2.js 1970-01-01 00:00:00 +0000
312+++ mixxx/res/controllers/Hercules-mp3e2.js 2012-08-25 14:32:20 +0000
313@@ -0,0 +1,275 @@
314+secondsBlink = 30;
315+
316+MP3e2 = new Object();
317+
318+function sendMsg(status, a, b) {
319+ midi.sendShortMsg(status, a, b);
320+ //controller.send([status, a, b], 3);
321+}
322+
323+blink = new Object();
324+
325+function light(id, enable) {
326+ if (enable == blink) {
327+ bval = 0x7f;
328+ val = 0;
329+ } else {
330+ bval = 0;
331+ val = enable?0x7f:0;
332+ }
333+ sendMsg(0x90, id, val);
334+ sendMsg(0x90, id + 0x30, bval);
335+}
336+
337+function simpleButton(id) {
338+ return function(val, group) {
339+ var btn = (group == "[Channel1]")?(id):(id+20);
340+
341+ light(btn, !!val);
342+ }
343+}
344+
345+function toggleButton(id, val, group) {
346+ var btn = (group == "[Channel1]")?(id):(id + 20);
347+
348+ light(btn, !!val);
349+}
350+
351+function btn_loop_enabled(val, group) {
352+ toggleButton(3, val, group);
353+ toggleButton(4, val, group);
354+}
355+
356+function btn_playposition(pos, group) {
357+ var secondsToEnd = engine.getValue(group, "duration") * (1 - pos);
358+ var btn = (group == "[Channel1]")?14:34;
359+
360+ if (secondsToEnd < 1) {
361+ light(btn, false);
362+ } else if (secondsToEnd < secondsBlink) {
363+ light(btn, blink);
364+ } else {
365+ light(btn, true);
366+ }
367+}
368+
369+binds = [
370+ ["loop_start_position", simpleButton(1)],
371+ ["loop_end_position", simpleButton(2)],
372+ ["hotcue_1_enabled", simpleButton(5)],
373+ ["hotcue_2_enabled", simpleButton(6)],
374+ ["hotcue_3_enabled", simpleButton(7)],
375+ ["hotcue_4_enabled", simpleButton(8)],
376+ ["play", simpleButton(15)],
377+ ["pfl", simpleButton(16)],
378+ ["playposition", btn_playposition],
379+ ["loop_enabled", btn_loop_enabled],
380+];
381+
382+meta = false;
383+ctl_id = "";
384+
385+MP3e2.init = function(id) {
386+ print("MP3e2 controller initialized: " + id);
387+
388+ ctl_id = id;
389+
390+ for (var i = 0; i < 2; i += 1) {
391+ group = "[Channel" + (i+1) + "]";
392+
393+
394+ for (var j in binds) {
395+ var control = binds[j][0];
396+ var func = binds[j][1];
397+
398+ engine.connectControl(group, control, func);
399+ engine.trigger(group, control);
400+ }
401+ }
402+
403+ // Turn everything off
404+ for (i = 1; i < 46; i += 1) {
405+ light(i, false);
406+ }
407+
408+ // Request all faders report position
409+ sendMsg(0xb0, 0x7f, 0x7f);
410+
411+ // Turn on some lights
412+ light(46, true); // Automix
413+ //light(14, true); // Cue A
414+ //light(34, true); // Cue B
415+}
416+
417+MP3e2.shutdown = function() {
418+ controller.close();
419+ print("MP3e2 controller shutdown: " + ctl_id);
420+}
421+
422+meta_buttons = {
423+ 1: "loop_start_position",
424+ 2: "loop_end_position",
425+ 5: "hotcue_1_clear",
426+ 6: "hotcue_2_clear",
427+ 7: "hotcue_3_clear",
428+ 8: "hotcue_4_clear",
429+};
430+
431+buttons = {
432+ 1: "loop_in",
433+ 2: "loop_out",
434+ 3: "reloop_exit",
435+ 4: "reloop_exit",
436+ 5: "hotcue_1_activate",
437+ 6: "hotcue_2_activate",
438+ 7: "hotcue_3_activate",
439+ 8: "hotcue_4_activate",
440+ 10: "rate_temp_down",
441+ 11: "rate_temp_up",
442+ 12: "back",
443+ 13: "fwd",
444+ 14: "cue_default",
445+ 15: "play",
446+ 16: "pfl",
447+ 17: "LoadSelectedTrack",
448+ 18: "beatsync",
449+
450+ 41: "SelectPrevTrack",
451+ 42: "SelectNextTrack",
452+};
453+
454+function handleButton(id, pressed) {
455+ var val = pressed ? 1 : 0;
456+ var did = id;
457+ var group, ctl;
458+
459+ if (id > 40) {
460+ group = '[Playlist]';
461+ } else if (id > 20) {
462+ group = '[Channel2]';
463+ did -= 20;
464+ } else {
465+ group = '[Channel1]';
466+ }
467+
468+ if (meta) {
469+ ctl = meta_buttons[did];
470+ }
471+ if (! ctl) {
472+ ctl = buttons[did];
473+ }
474+
475+ print(id + ": " + ctl);
476+ if (ctl) {
477+ switch (did) {
478+ case 1:
479+ case 2:
480+ case 5:
481+ case 6:
482+ case 7:
483+ case 8:
484+ if (meta) {
485+ val = -pressed;
486+ }
487+ break;
488+ case 15:
489+ case 16:
490+ // Ignore button release, toggle value
491+ if (! pressed) {
492+ return;
493+ }
494+ val = ! engine.getValue(group, ctl);
495+ break;
496+ }
497+
498+ engine.setValue(group, ctl, val);
499+ return;
500+ }
501+
502+ switch (id) {
503+ case 46:
504+ meta = !!pressed;
505+ light(30, meta);
506+ light(31, meta);
507+ light(10, meta);
508+ light(11, meta);
509+ light(19, meta);
510+ light(39, meta);
511+ break;
512+ }
513+}
514+
515+
516+faders = {
517+ 0x35: "filterHigh",
518+ 0x36: "filterMid",
519+ 0x37: "filterLow"
520+};
521+
522+function handleFader(id, val) {
523+ if (id < 0x34) {
524+ group = '[Channel' + ((id & 1) + 1) + ']';
525+ id = id & 0xfe;
526+ } else if (id < 0x39) {
527+ group = '[Channel1]';
528+ } else {
529+ group = '[Channel2]';
530+ id -= 5;
531+ }
532+
533+ ctl = faders[id];
534+
535+ switch (id) {
536+ case 0x30: // Jog wheel
537+ if (val == 127) {
538+ dir = -1;
539+ } else {
540+ dir = 1;
541+ }
542+ engine.setValue(group, "jog", dir);
543+ break;
544+ case 0x32: // Pitch adjust
545+ if (val == 127) {
546+ ctl = "rate_perm_down";
547+ } else {
548+ ctl = "rate_perm_up";
549+ }
550+ engine.setValue(group, ctl, 1);
551+ engine.setValue(group, ctl, 0);
552+ break;
553+ case 0x34:
554+ fval = script.absoluteLin(val, 0, 1);
555+ engine.setValue(group, "volume", fval);
556+ return;
557+ case 0x35:
558+ case 0x36:
559+ case 0x37:
560+ fval = script.absoluteNonLin(val, 0, 1, 4);
561+ engine.setValue(group, ctl, fval);
562+ break;
563+ case 0x38: // Crossfader
564+ fval = script.absoluteLin(val, -1, 1);
565+ engine.setValue('[Master]', 'crossfader', fval);
566+ break;
567+ }
568+}
569+
570+MP3e2.incomingData = function(data, length) {
571+ for (i = 0; i < length; i += 3) {
572+ switch (data[i]) {
573+ case 0x90:
574+ handleButton(data[i+1], data[i+2] == 0x7f);
575+ break;
576+ case 0xb0:
577+ handleFader(data[i+1], data[i+2]);
578+ break;
579+ default:
580+ print("Unhandled packet: " +
581+ " 0x" + data[i].toString(16) +
582+ " 0x" + data[i+1].toString(16) +
583+ " 0x" + data[i+2].teString(16))
584+ break;
585+ }
586+ }
587+}
588+
589
590=== added file 'mixxx/res/controllers/common-bulk-midi.js'
591--- mixxx/res/controllers/common-bulk-midi.js 1970-01-01 00:00:00 +0000
592+++ mixxx/res/controllers/common-bulk-midi.js 2012-08-25 14:32:20 +0000
593@@ -0,0 +1,5 @@
594+midi = new Object();
595+
596+midi.sendShortMsg = function(status, a, b) {
597+ controller.send([status, a, b], 3);
598+}
599
600=== added directory 'mixxx/src/controllers/bulk'
601=== added file 'mixxx/src/controllers/bulk/bulkcontroller.cpp'
602--- mixxx/src/controllers/bulk/bulkcontroller.cpp 1970-01-01 00:00:00 +0000
603+++ mixxx/src/controllers/bulk/bulkcontroller.cpp 2012-08-25 14:32:20 +0000
604@@ -0,0 +1,274 @@
605+/**
606+ * @file bulkcontroller.cpp
607+ * @author Neale Pickett neale@woozle.org
608+ * @date Thu Jun 28 2012
609+ * @brief USB Bulk controller backend
610+ *
611+ */
612+
613+#include "controllers/bulk/bulkcontroller.h"
614+#include "controllers/bulk/bulksupported.h"
615+
616+#include <time.h>
617+#include <stdio.h>
618+
619+
620+/* This is for my debugging, because I like typing
621+ DUMP();
622+ more than typing
623+ qDebug() << "foo";
624+ */
625+#ifdef NODUMP
626+# define DUMPf(fmt, args...)
627+#else
628+# define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
629+#endif
630+#define DUMP() DUMPf("")
631+#define DUMP_d(v) DUMPf("%s = %d", #v, v)
632+#define DUMP_x(v) DUMPf("%s = 0x%x", #v, v)
633+#define DUMP_s(v) DUMPf("%s = %s", #v, v)
634+#define DUMP_c(v) DUMPf("%s = '%c' (0x%02x)", #v, v, v)
635+#define DUMP_p(v) DUMPf("%s = %p", #v, v)
636+
637+BulkReader::BulkReader(libusb_device_handle *handle, unsigned char in_epaddr)
638+ : QThread() {
639+ m_phandle = handle;
640+ m_in_epaddr = in_epaddr;
641+}
642+
643+BulkReader::~BulkReader() {
644+ m_phandle = NULL;
645+}
646+
647+void BulkReader::stop() {
648+ m_stop = 1;
649+}
650+
651+void BulkReader::run() {
652+ m_stop = 0;
653+ unsigned char data[255];
654+
655+ while (m_stop == 0) {
656+ // Blocked polling: The only problem with this is that we can't close
657+ // the device until the block is released, which means the controller
658+ // has to send more data
659+ //result = hid_read_timeout(m_pHidDevice, data, 255, -1);
660+
661+ // This relieves that at the cost of higher CPU usage since we only
662+ // block for a short while (500ms)
663+ int transferred;
664+ int result;
665+
666+ result = libusb_bulk_transfer(m_phandle,
667+ m_in_epaddr,
668+ data, sizeof data,
669+ &transferred, 500);
670+ if (result >= 0) {
671+ //qDebug() << "Read" << result << "bytes, pointer:" << data;
672+ QByteArray outData((char *)data, transferred);
673+ emit(incomingData(outData));
674+ }
675+
676+ }
677+ qDebug() << "Stopped Reader";
678+}
679+
680+static QString
681+get_string(libusb_device_handle *handle, u_int8_t id)
682+{
683+ unsigned char buf[128];
684+ QString s;
685+
686+ buf[0] = 0;
687+ if (id) {
688+ (void)libusb_get_string_descriptor_ascii(handle, id, buf, sizeof buf);
689+ }
690+
691+ return QString::fromAscii((char *)buf);
692+}
693+
694+
695+BulkController::BulkController(libusb_device_handle *handle,
696+ struct libusb_device_descriptor *desc)
697+{
698+ vendor_id = desc->idVendor;
699+ product_id = desc->idProduct;
700+
701+ manufacturer = get_string(handle, desc->iManufacturer);
702+ product = get_string(handle, desc->iProduct);
703+ m_sUID = get_string(handle, desc->iSerialNumber);
704+
705+ setDeviceCategory(tr("USB Controller"));
706+
707+ setDeviceName(QString("%1 %2").arg(product).arg(m_sUID));
708+
709+ setInputDevice(true);
710+ setOutputDevice(true);
711+ m_pReader = NULL;
712+}
713+
714+BulkController::~BulkController() {
715+ close();
716+}
717+
718+void BulkController::visit(const MidiControllerPreset* preset) {
719+ Q_UNUSED(preset);
720+ // TODO(XXX): throw a hissy fit.
721+ qDebug() << "ERROR: Attempting to load a MidiControllerPreset to an HidController!";
722+}
723+
724+void BulkController::visit(const HidControllerPreset* preset) {
725+ m_preset = *preset;
726+ // Emit presetLoaded with a clone of the preset.
727+ emit(presetLoaded(getPreset()));
728+}
729+
730+bool BulkController::savePreset(const QString fileName) const {
731+ HidControllerPresetFileHandler handler;
732+ return handler.save(m_preset, getName(), fileName);
733+}
734+
735+bool BulkController::matchPreset(const PresetInfo& preset) {
736+ const QList< QHash<QString,QString> > products = preset.getProducts();
737+ QHash <QString, QString> product;
738+ foreach (product, products) {
739+ if (matchProductInfo(product))
740+ return true;
741+ }
742+ return false;
743+}
744+
745+bool BulkController::matchProductInfo(QHash <QString,QString > info) {
746+ int value;
747+ bool ok;
748+ // Product and vendor match is always required
749+ value = info["vendor_id"].toInt(&ok,16);
750+ if (!ok || vendor_id!=value) return false;
751+ value = info["product_id"].toInt(&ok,16);
752+ if (!ok || product_id!=value) return false;
753+
754+ // Match found
755+ return true;
756+}
757+
758+int BulkController::open() {
759+ int i;
760+
761+ if (isOpen()) {
762+ qDebug() << "USB Bulk device" << getName() << "already open";
763+ return -1;
764+ }
765+
766+ /* Look up endpoint addresses in supported database */
767+ for (i = 0; bulk_supported[i].vendor_id; i += 1) {
768+ if ((bulk_supported[i].vendor_id == vendor_id) &&
769+ (bulk_supported[i].product_id == product_id)) {
770+ in_epaddr = bulk_supported[i].in_epaddr;
771+ out_epaddr = bulk_supported[i].out_epaddr;
772+ break;
773+ }
774+ }
775+
776+ if (bulk_supported[i].vendor_id == 0) {
777+ qWarning() << "USB Bulk device" << getName() << "unsupported";
778+ return -1;
779+ }
780+ m_phandle = NULL;
781+
782+ // XXX: we should enumerate devices and match vendor, product, and serial
783+ if (m_phandle == NULL) {
784+ m_phandle = libusb_open_device_with_vid_pid(NULL, vendor_id, product_id);
785+ }
786+
787+ if (m_phandle == NULL) {
788+ qWarning() << "Unable to open USB Bulk device" << getName();
789+ return -1;
790+ }
791+
792+ setOpen(true);
793+ startEngine();
794+
795+ if (m_pReader != NULL) {
796+ qWarning() << "BulkReader already present for" << getName();
797+ } else {
798+ m_pReader = new BulkReader(m_phandle, in_epaddr);
799+ m_pReader->setObjectName(QString("BulkReader %1").arg(getName()));
800+
801+ connect(m_pReader, SIGNAL(incomingData(QByteArray)),
802+ this, SLOT(receive(QByteArray)));
803+
804+ // Controller input needs to be prioritized since it can affect the
805+ // audio directly, like when scratching
806+ m_pReader->start(QThread::HighPriority);
807+ }
808+
809+ return 0;
810+}
811+
812+int BulkController::close() {
813+ if (!isOpen()) {
814+ qDebug() << " device" << getName() << "already closed";
815+ return -1;
816+ }
817+
818+ qDebug() << "Shutting down USB Bulk device" << getName();
819+
820+ // Stop the reading thread
821+ if (m_pReader == NULL) {
822+ qWarning() << "BulkReader not present for" << getName()
823+ << "yet the device is open!";
824+ } else {
825+ disconnect(m_pReader, SIGNAL(incomingData(QByteArray)),
826+ this, SLOT(receive(QByteArray)));
827+ m_pReader->stop();
828+ if (debugging()) qDebug() << " Waiting on reader to finish";
829+ m_pReader->wait();
830+ delete m_pReader;
831+ m_pReader = NULL;
832+ }
833+
834+ // Stop controller engine here to ensure it's done before the device is closed
835+ // incase it has any final parting messages
836+ stopEngine();
837+
838+ // Close device
839+ if (debugging()) {
840+ qDebug() << " Closing device";
841+ }
842+ libusb_close(m_phandle);
843+ setOpen(false);
844+ return 0;
845+}
846+
847+void BulkController::send(QList<int> data, unsigned int length) {
848+ Q_UNUSED(length);
849+ QByteArray temp;
850+
851+ foreach (int datum, data) {
852+ temp.append(datum);
853+ }
854+ send(temp);
855+}
856+
857+void BulkController::send(QByteArray data) {
858+ int ret;
859+ int transferred;
860+
861+ // XXX: don't get drunk again.
862+ ret = libusb_bulk_transfer(m_phandle, out_epaddr,
863+ (unsigned char *)data.constData(), data.size(),
864+ &transferred, 0);
865+ if (ret < 0) {
866+ if (debugging()) {
867+ qWarning() << "Unable to send data to" << getName()
868+ << "serial #" << m_sUID << ":"
869+ << QString::fromAscii(libusb_error_name(ret));
870+ } else {
871+ qWarning() << "Unable to send data to" << getName() << ":"
872+ << QString::fromAscii(libusb_error_name(ret));
873+ }
874+ } else if (debugging()) {
875+ qDebug() << ret << "bytes sent to" << getName()
876+ << "serial #" << m_sUID;
877+ }
878+}
879
880=== added file 'mixxx/src/controllers/bulk/bulkcontroller.h'
881--- mixxx/src/controllers/bulk/bulkcontroller.h 1970-01-01 00:00:00 +0000
882+++ mixxx/src/controllers/bulk/bulkcontroller.h 2012-08-25 14:32:20 +0000
883@@ -0,0 +1,105 @@
884+/**
885+ * @file bulkcontroller.h
886+ * @author Neale Picket neale@woozle.org
887+ * @date Thu Jun 28 2012
888+ * @brief USB Bulk controller backend
889+ */
890+
891+#ifndef BULKCONTROLLER_H
892+#define BULKCONTROLLER_H
893+
894+#include <libusb.h>
895+
896+#include <QAtomicInt>
897+
898+#include "controllers/controller.h"
899+#include "controllers/hid/hidcontrollerpreset.h"
900+#include "controllers/hid/hidcontrollerpresetfilehandler.h"
901+
902+class BulkReader : public QThread {
903+ Q_OBJECT
904+ public:
905+ BulkReader(libusb_device_handle *handle, unsigned char in_epaddr);
906+ virtual ~BulkReader();
907+
908+ void stop();
909+
910+ signals:
911+ void incomingData(QByteArray data);
912+
913+ protected:
914+ void run();
915+
916+ private:
917+ libusb_device_handle *m_phandle;
918+ QAtomicInt m_stop;
919+ unsigned char m_in_epaddr;
920+};
921+
922+class BulkController : public Controller {
923+ Q_OBJECT
924+ public:
925+ BulkController(libusb_device_handle *handle, struct libusb_device_descriptor *desc);
926+ virtual ~BulkController();
927+
928+ virtual ControllerPresetPointer getPreset() const {
929+ HidControllerPreset* pClone = new HidControllerPreset();
930+ *pClone = m_preset;
931+ return ControllerPresetPointer(pClone);
932+ }
933+
934+ virtual bool savePreset(const QString fileName) const;
935+
936+ virtual ControllerPresetFileHandler* getFileHandler() const {
937+ return new HidControllerPresetFileHandler();
938+ }
939+
940+ virtual void visit(const MidiControllerPreset* preset);
941+ virtual void visit(const HidControllerPreset* preset);
942+
943+ virtual bool isMappable() const {
944+ return m_preset.isMappable();
945+ }
946+
947+ virtual bool matchPreset(const PresetInfo& preset);
948+ virtual bool matchProductInfo(QHash <QString,QString >);
949+
950+ protected:
951+ Q_INVOKABLE void send(QList<int> data, unsigned int length);
952+
953+ private slots:
954+ int open();
955+ int close();
956+
957+ private:
958+ // For devices which only support a single report, reportID must be set to
959+ // 0x0.
960+ virtual void send(QByteArray data);
961+
962+ virtual bool isPolling() const {
963+ return false;
964+ }
965+
966+ // Returns a pointer to the currently loaded controller preset. For internal
967+ // use only.
968+ virtual ControllerPreset* preset() {
969+ return &m_preset;
970+ }
971+
972+ libusb_device_handle *m_phandle;
973+
974+ // Local copies of things we need from desc
975+
976+ unsigned short vendor_id;
977+ unsigned short product_id;
978+ unsigned char in_epaddr;
979+ unsigned char out_epaddr;
980+ QString manufacturer;
981+ QString product;
982+
983+ QString m_sUID;
984+ BulkReader* m_pReader;
985+ HidControllerPreset m_preset;
986+};
987+
988+#endif
989
990=== added file 'mixxx/src/controllers/bulk/bulkenumerator.cpp'
991--- mixxx/src/controllers/bulk/bulkenumerator.cpp 1970-01-01 00:00:00 +0000
992+++ mixxx/src/controllers/bulk/bulkenumerator.cpp 2012-08-25 14:32:20 +0000
993@@ -0,0 +1,71 @@
994+/**
995+ * @file bulkenumerator.cpp
996+ * @author Neale Picket neale@woozle.org
997+ * @date Thu Jun 28 2012
998+ * @brief USB Bulk controller backend
999+ */
1000+
1001+#include <libusb.h>
1002+
1003+#include "controllers/bulk/bulkcontroller.h"
1004+#include "controllers/bulk/bulkenumerator.h"
1005+#include "controllers/bulk/bulksupported.h"
1006+
1007+BulkEnumerator::BulkEnumerator() : ControllerEnumerator() {
1008+ libusb_init(NULL);
1009+}
1010+
1011+BulkEnumerator::~BulkEnumerator() {
1012+ qDebug() << "Deleting USB Bulk devices...";
1013+ while (m_devices.size() > 0) {
1014+ delete m_devices.takeLast();
1015+ }
1016+ libusb_exit(NULL);
1017+}
1018+
1019+static int
1020+is_interesting(struct libusb_device_descriptor *desc)
1021+{
1022+ int i;
1023+
1024+ for (i = 0; bulk_supported[i].vendor_id; i += 1) {
1025+ if ((bulk_supported[i].vendor_id == desc->idVendor) &&
1026+ (bulk_supported[i].product_id == desc->idProduct)) {
1027+ return 1;
1028+ }
1029+ }
1030+
1031+ return 0;
1032+}
1033+
1034+QList<Controller*> BulkEnumerator::queryDevices() {
1035+ qDebug() << "Scanning USB Bulk devices:";
1036+
1037+ libusb_device **list;
1038+ ssize_t cnt = libusb_get_device_list(NULL, &list);
1039+ ssize_t i = 0;
1040+ int err = 0;
1041+
1042+ for (i = 0; i < cnt; i++) {
1043+ libusb_device *device = list[i];
1044+ struct libusb_device_descriptor desc;
1045+
1046+ libusb_get_device_descriptor(device, &desc);
1047+ if (is_interesting(&desc)) {
1048+ struct libusb_device_handle *handle;
1049+
1050+ err = libusb_open(device, &handle);
1051+ if (err) {
1052+ qDebug() << "Error opening a device";
1053+ continue;
1054+ }
1055+
1056+ BulkController* currentDevice = new BulkController(handle, &desc);
1057+ m_devices.push_back(currentDevice);
1058+ }
1059+ }
1060+
1061+ libusb_free_device_list(list, 1);
1062+
1063+ return m_devices;
1064+}
1065
1066=== added file 'mixxx/src/controllers/bulk/bulkenumerator.h'
1067--- mixxx/src/controllers/bulk/bulkenumerator.h 1970-01-01 00:00:00 +0000
1068+++ mixxx/src/controllers/bulk/bulkenumerator.h 2012-08-25 14:32:20 +0000
1069@@ -0,0 +1,24 @@
1070+/**
1071+* @file bulkenumerator.h
1072+* @author Neale Pickett neale@woozle.org
1073+* @date Thu Jun 28 2012
1074+* @brief Locate supported USB bulk controllers
1075+*/
1076+
1077+#ifndef BULKENUMERATOR_H
1078+#define BULKENUMERATOR_H
1079+
1080+#include "controllers/controllerenumerator.h"
1081+
1082+class BulkEnumerator : public ControllerEnumerator {
1083+ public:
1084+ BulkEnumerator();
1085+ virtual ~BulkEnumerator();
1086+
1087+ QList<Controller*> queryDevices();
1088+
1089+ private:
1090+ QList<Controller*> m_devices;
1091+};
1092+
1093+#endif
1094
1095=== added file 'mixxx/src/controllers/bulk/bulksupported.h'
1096--- mixxx/src/controllers/bulk/bulksupported.h 1970-01-01 00:00:00 +0000
1097+++ mixxx/src/controllers/bulk/bulksupported.h 2012-08-25 14:32:20 +0000
1098@@ -0,0 +1,24 @@
1099+/**
1100+* @file bulksupported.h
1101+* @author Neale Picket neale@woozle.org
1102+* @date Thu Jun 28 2012
1103+* @brief A list of supported USB bulk devices
1104+*/
1105+
1106+#ifndef BULKSUPPORTED_H
1107+#define BULKSUPPORTED_H
1108+
1109+typedef struct bulk_supported {
1110+ unsigned short vendor_id;
1111+ unsigned short product_id;
1112+ unsigned char in_epaddr;
1113+ unsigned char out_epaddr;
1114+} bulk_supported_t;
1115+
1116+static bulk_supported_t bulk_supported[] = {
1117+ {0x06f8, 0xb105, 0x82, 0x03}, // Hercules MP3e2
1118+ {0x06f8, 0xb100, 0x86, 0x06}, // Hercules Mk2
1119+ {0, 0, 0, 0}
1120+};
1121+
1122+#endif
1123
1124=== modified file 'mixxx/src/controllers/controllermanager.cpp'
1125--- mixxx/src/controllers/controllermanager.cpp 2012-07-08 23:27:47 +0000
1126+++ mixxx/src/controllers/controllermanager.cpp 2012-08-25 14:32:20 +0000
1127@@ -21,6 +21,10 @@
1128 #include "controllers/hid/hidenumerator.h"
1129 #endif
1130
1131+#ifdef __BULK__
1132+# include "controllers/bulk/bulkenumerator.h"
1133+#endif
1134+
1135 // http://developer.qt.nokia.com/wiki/Threads_Events_QObjects
1136
1137 // Poll every 1ms (where possible) for good controller response
1138@@ -77,6 +81,9 @@
1139 #ifdef __HSS1394__
1140 m_enumerators.append(new Hss1394Enumerator());
1141 #endif
1142+#ifdef __BULK__
1143+ m_enumerators.append(new BulkEnumerator());
1144+#endif
1145 #ifdef __HID__
1146 m_enumerators.append(new HidEnumerator());
1147 #endif