Implement v2 models. Add test fixture. Make MIDI port normal to zero

This commit is contained in:
hirsch 2016-07-17 20:44:54 -04:00
parent ae5b425bf2
commit df67030396
11 changed files with 623 additions and 55 deletions

View file

@ -3,6 +3,30 @@
Allow Fender Mustang series guitar amplifiers to be controlled by MIDI Allow Fender Mustang series guitar amplifiers to be controlled by MIDI
messages messages
# What's New
The command line parameter for MIDI controller port is now assumed to
start at 0 rather than 1 in order to match the way Linux ALSA
enumerates devices (see 'Run' below).
The program has been updated to run as a non-privileged daemon
process. You can still invoke it on the command line, but there will
be no output to the console and it no longer responds to keypress
input. Enter Ctrl-C (SIGINT) to exit.
I have added the first version of a runtime framework that starts and
stops the program automatically based on attached MIDI devices. This
seems to be working on a Beaglebone SBC, but has not been extensively
tested or documented yet. There is a small amount of customization
required to account for your specific amp model and MIDI controller
interface. Technically oriented users can probably work this out,
otherwise wait a bit until I can refine the packaging and arrange to
have the various pieces configured from a common setup file.
I'm currently working on implementation of amp and effects models
added in the Mustang v2 products and hope to have that checked in
soon.
# Introduction # Introduction
The intent is to implement the published MIDI spec for the Fender The intent is to implement the published MIDI spec for the Fender
@ -106,12 +130,28 @@ Both the amplifier and MIDI source should be connected first, then:
``` ```
$ mustang_midi midi_port# midi_listen_channel# $ mustang_midi midi_port# midi_listen_channel#
``` ```
NOTE: RPi and BBG are a bit fussy about enumeration of new USB NOTE1: I'm not sure about other platforms, but on Linux the MIDI
port number is equivalent to the ALSA card index. I had originally
treated port as 1..n, but since ALSA (and JACK? Not sure..) starts at
0, this has now been changed. You can find the card index for your
controller by connecting it to the computer and examining the
pseudo-file, e.g.:
$ cat /proc/asound/cards
0 [PCH ]: HDA-Intel - HDA Intel PCH
HDA Intel PCH at 0xf7530000 irq 30
1 [Interface ]: USB-Audio - USB MS1x1 MIDI Interface
M-Audio USB MS1x1 MIDI Interface at usb-0000:00:14.0-1, full speed
To accept MIDI messages from devices behind the M-Audio interface you
would now specify '1' as the MIDI port value.
NOTE2: RPi and BBG are a bit fussy about enumeration of new USB
devices. If you are not getting proper communication, quit the program devices. If you are not getting proper communication, quit the program
and try replugging both the Fender amp and MIDI controller **after** and try replugging both the Fender amp and MIDI controller **after**
those devices are powered up. those devices are powered up.
NOTE2: I've had success using a passive USB hub with the single USB on NOTE3: I've had success using a passive USB hub with the single USB on
the BBG, but YMMV since most USB<->5Pin MIDI converters draw some the BBG, but YMMV since most USB<->5Pin MIDI converters draw some
degree of bus power. A powered hub might be necessary in some degree of bus power. A powered hub might be necessary in some
situations. situations.

38
amp.h
View file

@ -10,6 +10,7 @@ class Mustang;
// F65 Deluxe // F65 Deluxe
// F65 Princeton // F65 Princeton
// F65 Twin // F65 Twin
// 60s Thrift
// //
class AmpCC { class AmpCC {
@ -51,13 +52,21 @@ private:
} }
// Cabinet // Cabinet
virtual int cc77( int value ) { virtual int cc77( int value ) {
if ( value < 1 || value > 12 ) return 0; if ( value > 12 ) return 0;
else return discrete_control( 0x11, 0x11, 0x8e, value ); else return discrete_control( 0x11, 0x11, 0x8e, value );
} }
// Dummy in base class // Dummy in base class
virtual int cc78( int value ) { return 0;} virtual int cc78( int value ) { return 0;}
virtual int cc79( int value ) { return 0;} virtual int cc79( int value ) { return 0;}
// Noise Gate Custom Threshold
virtual int cc90( int value ) {
if ( value > 9 ) return 0;
else return discrete_control( 0x10, 0x10, 0x86, value );
}
// Noise Gate Custom Depth
virtual int cc91( int value ) { return continuous_control( 0x09, 0x09, 0x0c, value );}
}; };
@ -104,6 +113,7 @@ private:
// British 80s // British 80s
// American 90s // American 90s
// Metal 2000 // Metal 2000
// British Watt
// //
class AmpCC4 : public AmpCC { class AmpCC4 : public AmpCC {
public: public:
@ -122,11 +132,37 @@ class AmpCC5 : public AmpCC {
public: public:
AmpCC5( Mustang * theAmp ) : AmpCC(theAmp) {} AmpCC5( Mustang * theAmp ) : AmpCC(theAmp) {}
private: private:
// No sag / bias
virtual int cc74( int value ) { return 0;}
virtual int cc75( int value ) { return 0;}
// No pres / master
virtual int cc78( int value ) { return 0;} virtual int cc78( int value ) { return 0;}
virtual int cc79( int value ) { return 0;} virtual int cc79( int value ) { return 0;}
}; };
// British Color
//
class AmpCC6 : public AmpCC {
public:
AmpCC6( Mustang * theAmp ) : AmpCC(theAmp) {}
private:
// Master Volume
virtual int cc79( int value ) { return continuous_control( 0x03, 0x03, 0x0c, value );}
};
// F57 Twin
//
class AmpCC7 : public AmpCC {
public:
AmpCC7( Mustang * theAmp ) : AmpCC(theAmp) {}
private:
// Presence
virtual int cc78( int value ) { return continuous_control( 0x07, 0x07, 0x0c, value );}
};
// Null Amp // Null Amp
// //
class NullAmpCC : public AmpCC { class NullAmpCC : public AmpCC {

View file

@ -93,3 +93,40 @@ static unsigned char metal_2k[] = {
0x00, 0x08, 0x08, 0x01, 0x00, 0x01, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 0x00, 0x08, 0x08, 0x01, 0x00, 0x01, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; };
// V2 Only:
static unsigned char studio_preamp[] = {
0x1c, 0x03, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x0d, 0x0d, 0x0d, 0x00,
0x00, 0x00, 0x0d, 0x01, 0x00, 0x01, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char sixties_thrift[] = {
0x1c, 0x03, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xac, 0x81, 0x81, 0x81, 0xac, 0x81, 0x9d, 0x81, 0x81, 0x81, 0x81, 0x81, 0x0f, 0x0f, 0x0f, 0x00,
0x00, 0x01, 0x0f, 0x01, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char brit_watts[] = {
0x1c, 0x03, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xba, 0x9d, 0x81, 0xff, 0xba, 0x98, 0xa6, 0x87, 0x81, 0x81, 0x81, 0x81, 0x11, 0x11, 0x11, 0x00,
0x00, 0x0a, 0x11, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char brit_color[] = {
0x1c, 0x03, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xac, 0x8c, 0x81, 0x81, 0x9d, 0x64, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x10, 0x10, 0x10, 0x01,
0x00, 0x08, 0x10, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char f57_twin[] = {
0x1c, 0x03, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xac, 0x4e, 0x81, 0x81, 0xac, 0x7b, 0x9d, 0xbd, 0x81, 0x81, 0x81, 0x81, 0x0e, 0x0e, 0x0e, 0x00,
0x00, 0x09, 0x0e, 0x01, 0x00, 0x01, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

View file

@ -65,6 +65,8 @@ class MultitapDelayCC : public DelayCC {
public: public:
MultitapDelayCC( Mustang * theAmp ) : DelayCC(theAmp) {} MultitapDelayCC( Mustang * theAmp ) : DelayCC(theAmp) {}
private: private:
// Delay Time
virtual int cc50( int value ) { return continuous_control( 0x01, 0x01, 0x08, value );}
// Feedback // Feedback
virtual int cc51( int value ) { return continuous_control( 0x02, 0x02, 0x01, value );} virtual int cc51( int value ) { return continuous_control( 0x02, 0x02, 0x01, value );}
// Brightness // Brightness

View file

@ -82,3 +82,26 @@ static unsigned char pitch_shifter[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; };
// V2 Only:
static unsigned char mod_wah[] = {
0x1c, 0x03, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf4, 0x00, 0x04, 0x01, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0x81, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char mod_touch_wah[] = {
0x1c, 0x03, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf5, 0x00, 0x04, 0x01, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xed, 0x81, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char diatonic_pitch_shift[] = {
0x1c, 0x03, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1f, 0x10, 0x04, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x56, 0x09, 0x04, 0x05, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

View file

@ -16,11 +16,25 @@
#include "mod_defaults.h" #include "mod_defaults.h"
#include "stomp_defaults.h" #include "stomp_defaults.h"
Mustang::usb_id Mustang::ids[] = {
{ OLD_USB_PID, 0x03, false },
{ NEW_USB_PID, 0xc1, false },
{ V2_USB_PID, 0x03, false },
{ MINI_USB_PID, 0x03, false },
{ FLOOR_USB_PID, 0x03, false },
{ BRONCO40_USB_PID, 0x03, false },
{ V2_III_PID, 0xc1, true },
{ V2_IV_PID, 0xc1, true },
{ 0, 0x00, false }
};
Mustang::Mustang() Mustang::Mustang()
{ {
amp_hand = NULL; amp_hand = NULL;
curr_amp = NULL; curr_amp = NULL;
tuner_active = false; tuner_active = false;
isV2 = false;
// "apply efect" command // "apply efect" command
memset(execute, 0x00, LENGTH); memset(execute, 0x00, LENGTH);
@ -46,6 +60,7 @@ Mustang::~Mustang()
int Mustang::start_amp(void) int Mustang::start_amp(void)
{ {
int ret, received; int ret, received;
static int init_value = -1;
unsigned char array[LENGTH]; unsigned char array[LENGTH];
if(amp_hand == NULL) if(amp_hand == NULL)
@ -55,17 +70,18 @@ int Mustang::start_amp(void)
if (ret) if (ret)
return ret; return ret;
// get handle for the device for ( int idx=0; ids[idx].pid != 0; idx++ ) {
if((amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, OLD_USB_PID)) == NULL) if ( (amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, ids[idx].pid)) != NULL ) {
if((amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, NEW_USB_PID)) == NULL) init_value = ids[idx].init_value;
if((amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, V2_USB_PID)) == NULL) isV2 = ids[idx].isV2;
if((amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, MINI_USB_PID)) == NULL) break;
if((amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, FLOOR_USB_PID)) == NULL) }
if((amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, BRONCO40_USB_PID)) == NULL) }
if((amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, V2_III_PID)) == NULL)
if((amp_hand = libusb_open_device_with_vid_pid(NULL, USB_VID, V2_IV_PID)) == NULL) if ( init_value < 0 ) {
{ // No amp found
libusb_exit( NULL ); libusb_exit( NULL );
fprintf( stderr, "S - No Mustang USB device found\n" );
return -100; return -100;
} }
@ -99,13 +115,7 @@ int Mustang::start_amp(void)
memset(array, 0x00, LENGTH); memset(array, 0x00, LENGTH);
array[0] = 0x1a; array[0] = 0x1a;
array[1] = init_value;
// This seems model specific
// Perhaps for Mustang II?
// array[1] = 0x03;
// Correct value for Mustang III
array[1] = 0xc1;
libusb_interrupt_transfer(amp_hand, 0x01, array, LENGTH, &received, TMOUT); libusb_interrupt_transfer(amp_hand, 0x01, array, LENGTH, &received, TMOUT);
libusb_interrupt_transfer(amp_hand, 0x81, array, LENGTH, &received, TMOUT); libusb_interrupt_transfer(amp_hand, 0x81, array, LENGTH, &received, TMOUT);
@ -230,8 +240,11 @@ int Mustang::tunerMode( int value )
void Mustang::updateAmpObj(void) { void Mustang::updateAmpObj(void) {
int curr = curr_state[AMP_STATE][MODEL]; int curr = curr_state[AMP_STATE][MODEL];
AmpCC * new_amp = NULL;
switch (curr) { switch (curr) {
case 0: case 0:
// No amp
break; break;
case F57_DELUXE_ID: case F57_DELUXE_ID:
@ -239,42 +252,51 @@ void Mustang::updateAmpObj(void) {
case F65_DELUXE_ID: case F65_DELUXE_ID:
case F65_PRINCETON_ID: case F65_PRINCETON_ID:
case F65_TWIN_ID: case F65_TWIN_ID:
delete curr_amp; case S60S_THRIFT_ID:
curr_amp = new AmpCC(this); new_amp = new AmpCC(this);
break; break;
case F59_BASSMAN_ID: case F59_BASSMAN_ID:
case BRIT_70S_ID: case BRIT_70S_ID:
delete curr_amp; new_amp = new AmpCC1(this);
curr_amp = new AmpCC1(this);
break; break;
case F_SUPERSONIC_ID: case F_SUPERSONIC_ID:
delete curr_amp; new_amp = new AmpCC2(this);
curr_amp = new AmpCC2(this);
break; break;
case BRIT_60S_ID: case BRIT_60S_ID:
delete curr_amp; new_amp = new AmpCC3(this);
curr_amp = new AmpCC3(this);
break; break;
case BRIT_80S_ID: case BRIT_80S_ID:
case US_90S_ID: case US_90S_ID:
case METAL_2K_ID: case METAL_2K_ID:
delete curr_amp; case BRIT_WATT_ID:
curr_amp = new AmpCC4(this); new_amp = new AmpCC4(this);
break; break;
case STUDIO_PREAMP_ID: case STUDIO_PREAMP_ID:
delete curr_amp; new_amp = new AmpCC5(this);
curr_amp = new AmpCC5(this); break;
case BRIT_COLOR_ID:
new_amp = new AmpCC6(this);
break;
case F57_TWIN_ID:
new_amp = new AmpCC7(this);
break; break;
default: default:
fprintf( stderr, "W - Amp id %x not supported yet\n", curr ); fprintf( stderr, "W - Amp id %x not supported yet\n", curr );
break; break;
} }
if ( (new_amp!=NULL) && (new_amp!=curr_amp) ) {
delete curr_amp;
curr_amp = new_amp;
}
} }
@ -538,9 +560,33 @@ int Mustang::setAmp( int ord ) {
array = metal_2k; array = metal_2k;
break; break;
default: default:
fprintf( stderr, "W - Amp select %d not yet supported\n", ord ); if ( isV2 ) {
switch (ord) {
case 13:
array = studio_preamp;
break;
case 14:
array = f57_twin;
break;
case 15:
array = sixties_thrift;
break;
case 16:
array = brit_watts;
break;
case 17:
array = brit_color;
break;
default:
fprintf( stderr, "W - Amp select %d not supported\n", ord );
return 0; return 0;
} }
}
else {
fprintf( stderr, "W - Amp select %d not supported\n", ord );
return 0;
}
}
// Setup amp personality // Setup amp personality
ret = libusb_interrupt_transfer(amp_hand, 0x01, array, LENGTH, &received, TMOUT); ret = libusb_interrupt_transfer(amp_hand, 0x01, array, LENGTH, &received, TMOUT);
@ -739,10 +785,28 @@ int Mustang::setMod( int ord ) {
case 11: case 11:
array = pitch_shifter; array = pitch_shifter;
break; break;
default:
if ( isV2 ) {
switch (ord) {
case 12:
array = mod_wah;
break;
case 13:
array = mod_touch_wah;
break;
case 14:
array = diatonic_pitch_shift;
break;
default: default:
fprintf( stderr, "W - Mod select %d not supported\n", ord ); fprintf( stderr, "W - Mod select %d not supported\n", ord );
return 0; return 0;
} }
}
else {
fprintf( stderr, "W - Mod select %d not supported\n", ord );
return 0;
}
}
array[FXSLOT] = curr_state[MOD_STATE][FXSLOT]; array[FXSLOT] = curr_state[MOD_STATE][FXSLOT];
@ -785,7 +849,13 @@ int Mustang::setStomp( int ord ) {
array = fuzz; array = fuzz;
break; break;
case 5: case 5:
if ( isV2 ) {
fprintf( stderr, "W - Stomp select %d not supported\n", ord );
return 0;
}
else {
array = fuzz_touch_wah; array = fuzz_touch_wah;
}
break; break;
case 6: case 6:
array = simple_comp; array = simple_comp;
@ -793,10 +863,34 @@ int Mustang::setStomp( int ord ) {
case 7: case 7:
array = compressor; array = compressor;
break; break;
default:
if ( isV2 ) {
switch (ord) {
case 8:
array = ranger_boost;
break;
case 9:
array = green_box;
break;
case 10:
array = orange_box;
break;
case 11:
array = black_box;
break;
case 12:
array = big_fuzz;
break;
default: default:
fprintf( stderr, "W - Stomp select %d not supported\n", ord ); fprintf( stderr, "W - Stomp select %d not supported\n", ord );
return 0; return 0;
} }
}
else {
fprintf( stderr, "W - Stomp select %d not supported\n", ord );
return 0;
}
}
array[FXSLOT] = curr_state[STOMP_STATE][FXSLOT]; array[FXSLOT] = curr_state[STOMP_STATE][FXSLOT];

View file

@ -33,7 +33,7 @@
// for USB communication // for USB communication
#define TMOUT 500 #define TMOUT 500
#define LENGTH 64 #define LENGTH 64
//#define NANO_SEC_SLEEP 10000000
// effect array fields // effect array fields
#define DSP 2 #define DSP 2
@ -110,8 +110,9 @@
#define BRIT_80S_ID 0x5e #define BRIT_80S_ID 0x5e
#define US_90S_ID 0x5d #define US_90S_ID 0x5d
#define METAL_2K_ID 0x6d #define METAL_2K_ID 0x6d
#define STUDIO_PREAMP_ID 0xf1
// v2 amp only
#define STUDIO_PREAMP_ID 0xf1
#define F57_TWIN_ID 0xf6 #define F57_TWIN_ID 0xf6
#define S60S_THRIFT_ID 0xf9 #define S60S_THRIFT_ID 0xf9
#define BRIT_WATT_ID 0xff #define BRIT_WATT_ID 0xff
@ -153,15 +154,30 @@
#define PHASER_ID 0x4f #define PHASER_ID 0x4f
#define PITCH_SHIFT_ID 0x1f #define PITCH_SHIFT_ID 0x1f
// v2 mod only
#define M_WAH_ID 0xf4
#define M_TOUCH_WAH_ID 0xf5
#define DIA_PSHIFT_ID 0x1f
// Stomp model id values // Stomp model id values
#define OVERDRIVE_ID 0x3c #define OVERDRIVE_ID 0x3c
#define WAH_ID 0x49 #define WAH_ID 0x49
#define TOUCH_WAH_ID 0x4a #define TOUCH_WAH_ID 0x4a
#define FUZZ_ID 0x1a #define FUZZ_ID 0x1a
// This is not present in v2:
#define FUZZ_TWAH_ID 0x1c #define FUZZ_TWAH_ID 0x1c
#define SIMPLE_COMP_ID 0x88 #define SIMPLE_COMP_ID 0x88
#define COMP_ID 0x07 #define COMP_ID 0x07
// v2 stomp only
#define RANGE_BOOST_ID 0x03
#define GREEN_BOX_ID 0xba
#define ORANGE_BOX_ID 0x10
#define BLACK_BOX_ID 0x11
#define BIG_FUZZ_ID 0x0f
class AmpCC; class AmpCC;
class ReverbCC; class ReverbCC;
@ -213,9 +229,24 @@ public:
}; };
private: private:
struct usb_id {
// product id
int pid;
// magic value for init packet
int init_value;
// v2?
bool isV2;
};
// For device probe
static usb_id ids[];
libusb_device_handle *amp_hand; // handle for USB communication libusb_device_handle *amp_hand; // handle for USB communication
unsigned char execute[LENGTH]; // "apply" command sent after each instruction unsigned char execute[LENGTH]; // "apply" command sent after each instruction
bool isV2;
// Current state of effects. Read from amp initially and maintained // Current state of effects. Read from amp initially and maintained
// as we change it. Major index is DSP# - 6, where: // as we change it. Major index is DSP# - 6, where:
// //

View file

@ -22,7 +22,7 @@ mustang_vid = 0x1ed8
mustang_pid = 0x0016 mustang_pid = 0x0016
# Controller MIDI device # Controller MIDI device
midi_device = 2 midi_device = 1
# MIDI listen channel # MIDI listen channel
midi_channel = 1 midi_channel = 1

View file

@ -19,9 +19,7 @@ void message_action( double deltatime, std::vector< unsigned char > *message, vo
#if 0 #if 0
unsigned int nBytes = message->size(); unsigned int nBytes = message->size();
if ( nBytes > 0 ) { if ( nBytes > 0 ) {
for ( unsigned int i=0; i<nBytes; i++ ) fprintf( stdout, "%02x %d %d\n", (int)message->at(0), (int)message->at(1), (int)message->at(2) );
std::cout << "Byte " << i << " = " << (int)message->at(i) << ", ";
std::cout << "stamp = " << deltatime << std::endl << std::flush;
} }
#endif #endif
@ -124,6 +122,7 @@ void message_action( double deltatime, std::vector< unsigned char > *message, vo
default: default:
break; break;
} }
} }
// void errorcallback( RtError::Type type, const std::string & detail, void *userData ) { // void errorcallback( RtError::Type type, const std::string & detail, void *userData ) {
@ -131,7 +130,9 @@ void message_action( double deltatime, std::vector< unsigned char > *message, vo
// } // }
void usage() { void usage() {
std::cerr << "Usage: prog usb_port midi_channel\n"; fprintf( stderr, "Usage: mustang_midi <controller_port#> <midi_channel#>\n" );
fprintf( stderr, " port = 0..n, channel = 1..16\n" );
exit( 1 ); exit( 1 );
} }
@ -140,7 +141,7 @@ int main( int argc, const char **argv ) {
char *endptr; char *endptr;
errno = 0; errno = 0;
int port = (int) strtol( argv[1], &endptr, 10 ) - 1; int port = (int) strtol( argv[1], &endptr, 10 );
if ( endptr == argv[0] ) usage(); if ( endptr == argv[0] ) usage();
if ( port < 0 ) usage(); if ( port < 0 ) usage();
@ -163,10 +164,8 @@ int main( int argc, const char **argv ) {
delete input_handler; delete input_handler;
return 8; return 8;
} }
// n.b. Midi port rank on the host system is 1..n, but this method
// is normal to 0.
input_handler->openPort( port ); input_handler->openPort( port );
// input_handler->openVirtualPort( "TestPort" );
input_handler->setCallback( &message_action ); input_handler->setCallback( &message_action );
// Don't want sysex, timing, active sense // Don't want sysex, timing, active sense

View file

@ -33,6 +33,8 @@ static unsigned char fuzz[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; };
// Fuzz Touch Wah in original only!
static unsigned char fuzz_touch_wah[] = { static unsigned char fuzz_touch_wah[] = {
0x1c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -54,3 +56,40 @@ static unsigned char compressor[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; };
// V2 Only:
static unsigned char ranger_boost[] = {
0x1c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x01, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x64, 0xba, 0x01, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char green_box[] = {
0x1c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xba, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x81, 0xb1, 0x8c, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char orange_box[] = {
0x1c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x01, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x81, 0x81, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char black_box[] = {
0x1c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x01, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x81, 0x81, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static unsigned char big_fuzz[] = {
0x1c, 0x03, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0f, 0x01, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xac, 0xac, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

267
test.py Executable file
View file

@ -0,0 +1,267 @@
#!/usr/bin/python
# Use aconnectgui to wire Midi Through Port-0 out to
# mustang bridge virtual input port name
# OR
# hirsch@z87:~$ aconnect -o
# client 14: 'Midi Through' [type=kernel]
# 0 'Midi Through Port-0'
# client 128: 'RtMidi Input Client' [type=user]
# 0 'TestPort '
#
# Given the above, open RtMidi as: 'RtMidi Input Client 128:0'
#
# outport = mido.open_output('Midi Through 14:0')
from time import sleep
import sys
import itertools as it
import mido
mido.set_backend('mido.backends.rtmidi')
pc = mido.Message('program_change')
cc = mido.Message('control_change')
def analog_send( outport, sleeptime=0.25 ):
for value in [ 0, 32, 64, 96, 127, 96, 64, 32, 0 ]:
cc.value = value
outport.send( cc )
sleep( sleeptime )
def discrete_send( outport, max ):
for value in it.chain( range(0,max+1), range(max,-1,-1) ):
cc.value = value
outport.send( cc )
sleep( 0.25 )
# Screen 1 is the same for all models
def amp_screen1( outport ):
for i in range( 69, 74 ):
cc.control = i
analog_send( outport )
# Some variation in screen 2 across models
def amp_screen2( outport, bias_sag, extra ):
if bias_sag:
cc.control = 74
discrete_send( outport, 2 )
cc.control = 75
analog_send( outport )
cc.control = 76
discrete_send( outport, 4 )
cc.control = 77
discrete_send( outport, 12 )
if extra:
cc.control = 78
analog_send( outport )
cc.control = 79
analog_send( outport )
# Step through all amp models
def amp_select( outport ):
cc.control = 68
for i in range( 0, 13 ):
cc.value = i
outport.send( cc )
sleep( 0.5 )
def run_amp_test( struct, outport ):
raw_input( "Hit ENTER to run amp model select test...\n" )
amp_select( outport )
for i in range( 0, len(struct) ):
amp_rec = struct[i]
raw_input( "Hit ENTER to run parm edit check for %s\n" % amp_rec[0] )
cc.control = 68
cc.value = amp_rec[1]
outport.send( cc )
raw_input( "Enter amp edit mode on Mustang and hit ENTER to proceed...\n" )
amp_screen1( outport )
raw_input( "Step to amp edit screen 2 and hit ENTER...\n" )
amp_screen2( outport, amp_rec[2], amp_rec[3] )
# Step through all reverb models
def reverb_select( outport ):
cc.control = 58
for i in range( 0, 11 ):
cc.value = i
outport.send( cc )
sleep( 0.5 )
def run_reverb_test( outport ):
raw_input( "Hit ENTER to run reverb model select test...\n" )
reverb_select( outport )
raw_input( "Enter Reverb edit mode and hit ENTER...\n" )
for i in range ( 59, 64 ):
cc.control = i
analog_send( outport )
sleep( 0.25 )
# Step through all delay models
def delay_select( outport ):
cc.control = 48
for i in range( 0, 10 ):
cc.value = i
outport.send( cc )
sleep( 0.5 )
def run_delay_test( struct, outport ):
raw_input( "Hit ENTER to run delay model select test...\n" )
delay_select( outport )
for i in range( 0, len(struct) ):
delay_rec = struct[i]
raw_input( "Hit ENTER to run parm edit check for %s\n" % delay_rec[0] )
cc.control = 48
cc.value = delay_rec[1]
outport.send( cc )
raw_input( "Enter delay edit mode on Mustang and hit ENTER to proceed...\n" )
for i in range( 49, 54 ):
cc.control = i
# Multitap Delay has a single discrete control
if i==53 and delay_rec[2]:
discrete_send( outport, 3 )
else:
analog_send( outport )
if delay_rec[3]:
analog_send( outport )
# Step through all mod models
def mod_select( outport ):
cc.control = 38
for i in range( 0, 12 ):
cc.value = i
outport.send( cc )
sleep( 0.5 )
def run_mod_test( struct, outport ):
raw_input( "Hit ENTER to run mod model select test...\n" )
mod_select( outport )
for i in range( 0, len(struct) ):
mod_rec = struct[i]
raw_input( "Hit ENTER to run parm edit check for %s\n" % mod_rec[0] )
cc.control = 38
cc.value = mod_rec[1]
outport.send( cc )
raw_input( "Enter mod edit mode on Mustang and hit ENTER to proceed...\n" )
for i in range( 39, 44 ):
cc.control = i
if (i==42 and mod_rec[2]) or (i==43 and mod_rec[3]):
discrete_send( outport, 1 )
else:
analog_send( outport )
# Step through all stomp models
def stomp_select( outport ):
cc.control = 28
for i in range( 0, 8 ):
cc.value = i
outport.send( cc )
sleep( 0.5 )
def run_stomp_test( struct, outport ):
raw_input( "Hit ENTER to run stomp model select test...\n" )
stomp_select( outport )
for i in range( 0, len(struct) ):
stomp_rec = struct[i]
raw_input( "Hit ENTER to run parm edit check for %s\n" % stomp_rec[0] )
cc.control = 28
cc.value = stomp_rec[1]
outport.send( cc )
raw_input( "Enter stomp edit mode on Mustang and hit ENTER to proceed...\n" )
for i in range( 29, 34 ):
cc.control = i
if i==33 and stomp_rec[2]:
discrete_send( outport, 1 )
elif i==29 and stomp_rec[3]:
discrete_send( outport, 3 )
break
else:
analog_send( outport )
# Step through program changes
def program_change_test( outport ):
raw_input( "Hit ENTER to run program change test...\n" )
for i in ( 0, 25, 75, 99, 60, 40, 20, 0 ):
pc.program = i
outport.send( pc )
sleep( 0.5 )
args = sys.argv
if not len(args) == 3:
print "Usage: test.py <virtual_port_name> <midi_channel>\n"
sys.exit( 1 )
try:
pc.channel = cc.channel = int( args[2] ) - 1
except ValueError:
print "Arg2 must be numeric!\n"
sys.exit( 1 )
outport = mido.open_output( args[1] )
program_change_test( outport )
# Model # 33 Dig?
stomp_test = (
( "Overdrive", 1, False, False ),
( "Wah", 2, True, False ),
( "Simple Comp", 6, False, True ),
( "Comp", 7, False, False )
)
run_stomp_test( stomp_test, outport )
# Model # 42 Dig? 43 Dig?
mod_test = (
( "Sine Chorus", 1, False, False ),
( "Vibratone", 5, False, False ),
( "Vintage Trem", 6, False, False ),
( "Ring Mod", 8, True, False ),
( "Phaser", 10, False, True ),
( "Pitch Shift", 11, False, False )
)
run_mod_test( mod_test, outport )
run_reverb_test( outport )
# Model # 53 Dig?, Has 54?
delay_test = (
( "Mono Delay", 1, False, False ),
( "Multitap", 4, True, False ),
( "Tape Delay", 8, False, False )
)
run_delay_test( delay_test, outport )
# Model, #, Bias/Sag?, Extra?
amp_test = (
( "Fender 65 Twin", 6, True, False ),
( "Fender SuperSonic", 7, True, True ),
( "British 60s", 8, True, True ),
( "British 70s", 9, True, True ),
( "British 80s", 10, True, True )
)
run_amp_test( amp_test, outport )