Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First steps towards support for Mustang/Rumble LT series amps #28

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1aa4f89
Merge pull request #1 from offa/master
tim-littlefair Sep 25, 2023
a4e28bf
Added note advising names of Ubuntu/Debian packages required to deliv…
tim-littlefair Sep 25, 2023
a9deb6e
LT40S being recognized, but not attempting to process it yet
tim-littlefair Sep 25, 2023
d1e14c9
Merging back upstream changes after https://github.com/offa/plug/pull/19
tim-littlefair Oct 6, 2024
e6dd8fe
As of this commit, the name of the LT 40S is captured, but parsing am…
tim-littlefair Oct 6, 2024
c69ad2f
Added PID for Mustang Micro - as of this commit report for the device…
tim-littlefair Oct 6, 2024
34bf91e
Added CLI option for V3 device enablement
tim-littlefair Oct 7, 2024
1bd6303
WIP on CLI flag for V3USB feature
tim-littlefair Oct 7, 2024
79feee6
Mustang class now handles deserialization separately for new category…
tim-littlefair Oct 9, 2024
b232f3a
App now runs gracefully when connected to LT40S
tim-littlefair Oct 9, 2024
d3afee6
Cloned MustangTest.cpp as a basis for a test for V3USB functionality …
tim-littlefair Oct 9, 2024
116bda7
Added test for v3 recognition and current lack of functionality
tim-littlefair Oct 11, 2024
80bfafe
Re-enabled attempt to read name payload
tim-littlefair Oct 11, 2024
6d28b92
Added kdevelop project
tim-littlefair Oct 11, 2024
7467639
Script to add udev rules to let non-root users access any Fender/Must…
tim-littlefair Oct 11, 2024
7f97d60
Not running as root any more
tim-littlefair Oct 11, 2024
7f1284a
New init function for V3_USB added - 24 packets returned
tim-littlefair Oct 11, 2024
9fff8ee
Tweaked debug output
tim-littlefair Oct 12, 2024
7e25b0e
Added wordy comment about limited support for LT series and less for …
tim-littlefair Oct 12, 2024
539bb13
Commented out whole of MustangV3UsbTest pending fixing it
tim-littlefair Oct 12, 2024
c7dbe63
cmake build directory debug should never have been in git
tim-littlefair Oct 12, 2024
a386185
Commented out whole of MustangV3UsbTest pending fixing it
tim-littlefair Oct 12, 2024
fec90e3
Working to resolve clang-format-lint issues in https://github.com/off…
tim-littlefair Oct 12, 2024
1123205
Working to resolve clang-format-lint issues in https://github.com/off…
tim-littlefair Oct 12, 2024
55249ee
Working to resolve clang-format-lint issues in https://github.com/off…
tim-littlefair Oct 12, 2024
511e05a
Working to resolve clang-format-lint issues in https://github.com/off…
tim-littlefair Oct 12, 2024
814cb57
Merge branch 'tims' of github.com:tim-littlefair/plug into tims
tim-littlefair Oct 12, 2024
f74efa4
Working to resolve clang-format-lint issues in https://github.com/off…
tim-littlefair Oct 12, 2024
19eadaf
Working to resolve clang-format-lint issues in https://github.com/off…
tim-littlefair Oct 12, 2024
6c6b181
Working to resolve clang-format-lint issues in https://github.com/off…
tim-littlefair Oct 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@ build/*
CMakeUserPresets.json
cppcheck-*
*.cppcheck

# Tim Littlefair, October 2024
# When I created a kdevelop project, kdevelop created a directory
# .kdev4 which contains a single file .kdev4/offa-plug.kdev4.
# As this file contains the full path my local build directory
# I'm guessing that it should not be committed to git.
.kdev4
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to submit this line directly, extending .gitignore might help others too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the invitation is here - do you want me to create a PR with the file ./offa-plug.kdev4 in it, and whatever else is required so that the kdevelop project works out of the box on a fresh clone?

I can't see the point of adding a line for the .kdev4 subdirectory to .gitignore unless it is alongside the project file. I added this because the files the in .kdev4 subdirectory contained information the full path to the sandbox directory on my computer, so I'm guessing that these files are not particularly helpful to others (and might be harmful).

Experimentation shows that adding the offa-plug.kdev4 file only doesn't seem to give a clean startup so I'll look into what is required for a path-neutral project and I'm happy to do a PR containing this if it is what is preferred.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the invitation is here - do you want me to create a PR with the file ./offa-plug.kdev4 in it, and whatever else is required so that the kdevelop project works out of the box on a fresh clone?

No, just add project files to the .gitignore so others that might use the IDE don't commit their local files accidentally.

6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Please see [Contributing](CONTRIBUTING.md) for how to contribute to this project
- [**Qt6**](https://www.qt.io/)
- [**libusb-1.0**](http://libusb.info/)

For developers on Ubuntu or other Debian-based distributions, appropriate versions of several of the required packages are unlikely to be installed by default.
As of Ubuntu 24.04 Noble Numbat, the following command is recommended to find the packages required:
'''
sudo apt-get install -y cmake qtcreator qtbase6-dev qt6-qmake libusb-1.0-0-dev \
cmake-googletest googletest libgtest-dev google-mock libgmock-dev
'''

## Building

Expand Down
5 changes: 5 additions & 0 deletions cmake/50-mustang.rules
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ ATTRS{idVendor}=="1ed8", ATTRS{idProduct}=="0015", ENV{ID_AUDIO_MODELING_AMP}="1
ATTRS{idVendor}=="1ed8", ATTRS{idProduct}=="0016", ENV{ID_AUDIO_MODELING_AMP}="1"
ATTRS{idVendor}=="1ed8", ATTRS{idProduct}=="0017", ENV{ID_AUDIO_MODELING_AMP}="1"

# Mustang/Rumble LT series (experimental, even less warranty than usual)
ATTRS{idVendor}=="1ed8", ATTRS{idProduct}=="0037", ENV{ID_AUDIO_MODELING_AMP}="1"
ATTRS{idVendor}=="1ed8", ATTRS{idProduct}=="0038", ENV{ID_AUDIO_MODELING_AMP}="1"
ATTRS{idVendor}=="1ed8", ATTRS{idProduct}=="0046", ENV{ID_AUDIO_MODELING_AMP}="1"

LABEL="mustang_plug_rules_end"
1 change: 1 addition & 0 deletions doc/USB.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@
| Mustang LT 40S | 1ed8 | 0046 | |
| Rumble LT 25 | 1ed8 | 0038 | |
| Mustang GT 40 | 1ed8 | 0032 | |
| Mustang Micro | 1ed8 | 0043 | |
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, could you submit this in a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do shortly. Unfortunately, experimentation to date shows that the Mustang Micro is completely unresponsive to the commands which are getting responses on the LT40S, so this may only be useful to put a disclaimer to say this device is not supportable. It is not surprising, as the 'Micro is not interoperable with the Desktop Fender Tone app either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR for this will come in in a couple of minutes.

14 changes: 14 additions & 0 deletions include/DeviceModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ namespace plug
{
MustangV1,
MustangV2,

// Mustang LT 25, LT 40S, LT 50, Rumble LT 25
// All of these are interoperable over USB with
// Windows/macOS Fender Tone
MustangV3_USB,

// Mustang GT/GTX series, also Mustang Micro Plus
// (all theoretical - no test devices available)
// According to Fender marketing material, these are interoperable
// over Bluetooth with iOS/Android Fender TONE 4.0 app.
// No support yet, defining the symbol for future use
MustangV3_BT,


Other
};

Expand Down
2 changes: 1 addition & 1 deletion include/com/ConnectionFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ namespace plug::com
class Mustang;


std::unique_ptr<Mustang> connect();
std::unique_ptr<Mustang> connect(bool v3usb_devices_enabled);
}
2 changes: 1 addition & 1 deletion include/com/PacketSerializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ namespace plug::com
Packet<EmptyPayload> serializeApplyCommand(fx_pedal_settings effect);

std::array<Packet<EmptyPayload>, 2> serializeInitCommand();

std::array<Packet<EmptyPayload>, 3> serializeInitCommand_V3_USB();
}
1 change: 1 addition & 0 deletions include/com/UsbDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ namespace plug::com::usb

std::size_t write(std::uint8_t endpoint, std::uint8_t* data, std::size_t dataSize);
std::vector<std::uint8_t> receive(std::uint8_t endpoint, std::size_t dataSize);
std::vector<std::uint8_t> receive_more(std::uint8_t endpoint, std::size_t dataSize);

Device& operator=(Device&&) = default;

Expand Down
5 changes: 5 additions & 0 deletions include/ui/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ namespace plug
MainWindow(const MainWindow&) = delete;
~MainWindow() override;

static void enable_v3usb_devices();

MainWindow& operator=(const MainWindow&) = delete;

public slots:
Expand Down Expand Up @@ -96,6 +98,9 @@ namespace plug
SaveToFile* saver;
QuickPresets* quickpres;

static bool v3usb_devices_enabled;


private slots:
void about();
void showEffect(std::uint8_t slot);
Expand Down
4 changes: 4 additions & 0 deletions offa-plug.kdev4
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[Project]
CreatedFrom=CMakeLists.txt
Manager=KDevCMakeManager
Name=offa-plug
54 changes: 54 additions & 0 deletions script/manage_nonroot_udev_access.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/sh

usage() {
cat <<EOF

Usage:
$0 enable - enable non-root users to access all USB devices with Fender Mustang vendor id 1ed8
$0 disable - revoke non-root user access if previously granted
Both variants require sudo access

EOF
}

udev_rules_filename=/etc/udev/rules.d/10-all_users_access_usb_fender_mustang.rules

if [ "$1" = "--help" ]
then

usage

exit 0

elif [ "$1" = "enable" ]
then

echo '

# Enable all users on this computer to interact with Fender Mustang devices with USB vendor id 1ed8
# Useful for contributors to https://github.com/offa/plug to enable them to experiment with
# Fender devices not already supported.
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="1ed8", GROUP="users"

' | sudo tee $udev_rules_filename > /dev/null

sudo udevadm control --reload

exit 0

elif [ "$1" = "disable" ]
then

sudo rm $udev_rules_filename

sudo udevadm control --reload

exit 0

else

usage

exit 1

fi
24 changes: 24 additions & 0 deletions src/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "ui/mainwindow.h"
#include "Version.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>

int main(int argc, char* argv[])
{
Expand All @@ -32,9 +34,31 @@ int main(int argc, char* argv[])
QCoreApplication::setApplicationName("Plug");
QCoreApplication::setApplicationVersion(QString::fromStdString(plug::version()));

// following https://doc.qt.io/qt-6/qcommandlineparser.html
QCommandLineParser parser;
parser.setApplicationDescription("Linux program to provide similar services to Fender Fuse/Fender Tone");
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption enableV3UsbDevicesOption(
"enable-v3usb-devices",
QCoreApplication::translate(
"main",
"Enable incomplete support for USB connected V3 devices controllable with Windows/macOS FenderTone applications "
"(Mustang LT25/LT40S/LT50, Rumble LT 25)."
// TODO: Mustang LT50 PID not integrated in DeviceModel.cpp yet
));
parser.addOption(enableV3UsbDevicesOption);
parser.process(app);

plug::com::usb::Context context{};

if (parser.isSet(enableV3UsbDevicesOption))
{
plug::MainWindow::enable_v3usb_devices();
}
plug::MainWindow window;


window.show();

return app.exec();
Expand Down
57 changes: 54 additions & 3 deletions src/com/ConnectionFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ namespace plug::com
inline constexpr std::uint16_t mustangFloor{0x0012};
inline constexpr std::uint16_t mustangI_II_v2{0x0014};
inline constexpr std::uint16_t mustangIII_IV_V_v2{0x0016};

inline constexpr std::uint16_t mustangLT25{0x0037};
inline constexpr std::uint16_t rumbleLT25{0x0038};
inline constexpr std::uint16_t mustangLT40S{0x0046};
inline constexpr std::uint16_t mustangMicro{0x0043};
}

inline constexpr std::initializer_list<std::uint16_t> pids{
Expand All @@ -50,7 +55,13 @@ namespace plug::com
usbPID::mustangMini,
usbPID::mustangFloor,
usbPID::mustangI_II_v2,
usbPID::mustangIII_IV_V_v2};
usbPID::mustangIII_IV_V_v2,

usbPID::mustangMicro,
usbPID::mustangLT25,
usbPID::rumbleLT25,
usbPID::mustangLT40S,
};

DeviceModel getModel(std::uint16_t pid)
{
Expand All @@ -70,14 +81,49 @@ namespace plug::com
return DeviceModel{"Mustang I/II", DeviceModel::Category::MustangV2, 24};
case usbPID::mustangIII_IV_V_v2:
return DeviceModel{"Mustang III/IV/V", DeviceModel::Category::MustangV2, 100};

// The economical LT series Mustang/Rumble series, released 2019 to 2020s speak a
// quite different USB protocol from offa-plug. Fender issue applications
// branded Fender Tone for Windows and macOS which offers comparable features
// to offa-plug and the original (now obsolete) Windows/macOS Fender Plug applications
// which was provided to control the devices identified by offa-plug as
// with the DeviceModel::Category::MustangV1 and ..::MustangV2 constants.
// If offa-plug runs with argument --enable-v3usb_devices these will be detected
// and some data will be exchanged.
// A lot more work will be required to interpret this data and implement commands which can
// be triggered from the offa-plug GUI.
case usbPID::mustangLT25:
return DeviceModel{"Mustang LT 25", DeviceModel::Category::MustangV3_USB, 50};
case usbPID::mustangLT40S:
return DeviceModel{"Mustang LT 40S", DeviceModel::Category::MustangV3_USB, 50};

// TODO: add mustangLT50 support when PID is known

// The Rumble LT25 is believed to be similar protocol wise to the Mustang LT series
case usbPID::rumbleLT25:
return DeviceModel{"Rumble LT 25", DeviceModel::Category::MustangV3_USB, 50};

// Testing to date suggest that the Mustang Micro does not respond
// to any of the USB commands sent by the Fender Tone USB version, which is disappointing
// but not surprising given that Fender Tone doesn't interact with this device.
// case usbPID::mustangMicro:
// return DeviceModel{"Mustang Micro", DeviceModel::Category::MustangV3_USB, 0};

// The premium GT and GTX series, released from around 2017 are designed to be
// controlled over Bluetooth by iOS/Android mobile applications rather than
// over USB by Windows/macOS applications.
// It is unlikely that offa-plug will ever become interoperable with these, but the
// enumeration value DeviceModel::Category::MustangV3_BT has been reserved for use
// in the event that this should ever happen.

default:
throw CommunicationException{"Unknown device pid: " + std::to_string(pid)};
}
}

}

std::unique_ptr<Mustang> connect()
std::unique_ptr<Mustang> connect(bool v3usb_devices_enabled)
{
auto devices = usb::listDevices();

Expand All @@ -89,7 +135,12 @@ namespace plug::com
{
throw CommunicationException{"No device found"};
}
return std::make_unique<Mustang>(getModel(itr->productId()), std::make_shared<UsbComm>(std::move(*itr)));
std::unique_ptr<Mustang> retval = std::make_unique<Mustang>(getModel(itr->productId()), std::make_shared<UsbComm>(std::move(*itr)));
if (v3usb_devices_enabled == false && retval->getDeviceModel().category() == DeviceModel::Category::MustangV3_USB)
{
throw CommunicationException{"V3 USB device found but not enabled"};
}
return retval;
}

}
54 changes: 42 additions & 12 deletions src/com/Mustang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/


#include "com/Mustang.h"
#include "com/PacketSerializer.h"
#include "com/CommunicationException.h"
Expand All @@ -27,19 +28,39 @@

namespace plug::com
{
SignalChain decode_data(const std::array<PacketRawType, 7>& data)
SignalChain decode_data(const std::array<PacketRawType, 7>& data, DeviceModel model)
{
const auto name = decodeNameFromData(fromRawData<NamePayload>(data[0]));
const auto amp = decodeAmpFromData(fromRawData<AmpPayload>(data[1]), fromRawData<AmpPayload>(data[6]));
const auto effects = decodeEffectsFromData({{fromRawData<EffectPayload>(data[2]), fromRawData<EffectPayload>(data[3]),
fromRawData<EffectPayload>(data[4]), fromRawData<EffectPayload>(data[5])}});
switch (model.category())
{
case DeviceModel::Category::MustangV1:
case DeviceModel::Category::MustangV2:
{
const auto name = decodeNameFromData(fromRawData<NamePayload>(data[0]));
const auto amp = decodeAmpFromData(fromRawData<AmpPayload>(data[1]), fromRawData<AmpPayload>(data[6]));
const auto effects = decodeEffectsFromData({{fromRawData<EffectPayload>(data[2]), fromRawData<EffectPayload>(data[3]),
fromRawData<EffectPayload>(data[4]), fromRawData<EffectPayload>(data[5])}});

return SignalChain{name, amp, effects};
}

case DeviceModel::Category::MustangV3_USB:
{
const auto name = decodeNameFromData(fromRawData<NamePayload>(data[0]));
const amp_settings amp{};
const std::vector<fx_pedal_settings> effects;
return SignalChain{name, amp, effects};
}

return SignalChain{name, amp, effects};
case DeviceModel::Category::MustangV3_BT:
default:
throw new CommunicationException("Amplifier does not belong to a supported category");
}
}

std::vector<std::uint8_t> receivePacket(Connection& conn)
{
return conn.receive(packetRawTypeSize);
std::vector<std::uint8_t> retval = conn.receive(packetRawTypeSize);
return retval;
}


Expand Down Expand Up @@ -131,7 +152,7 @@ namespace plug::com

SignalChain Mustang::load_memory_bank(std::uint8_t slot)
{
return decode_data(loadBankData(*conn, slot));
return decode_data(loadBankData(*conn, slot), model);
}

void Mustang::save_effects(std::uint8_t slot, std::string_view name, const std::vector<fx_pedal_settings>& effects)
Expand Down Expand Up @@ -181,13 +202,22 @@ namespace plug::com
std::array<PacketRawType, 7> presetData{{}};
std::copy(std::next(recieved_data.cbegin(), numPresetPackets), std::next(recieved_data.cbegin(), numPresetPackets + 7), presetData.begin());

return {decode_data(presetData), presetNames};
return {decode_data(presetData, model), presetNames};
}

void Mustang::initializeAmp()
{
const auto packets = serializeInitCommand();
std::for_each(packets.cbegin(), packets.cend(), [this](const auto& p)
{ sendCommand(*conn, p.getBytes()); });
if (model.category() == DeviceModel::Category::MustangV3_USB)
{
const auto packets = serializeInitCommand_V3_USB();
std::for_each(packets.cbegin(), packets.cend(), [this](const auto& p)
{ sendCommand(*conn, p.getBytes()); });
}
else
{
const auto packets = serializeInitCommand();
std::for_each(packets.cbegin(), packets.cend(), [this](const auto& p)
{ sendCommand(*conn, p.getBytes()); });
}
}
}
Loading