Hi, I have an idea that might solve your problem. I wrote some code that could serve as an example for you. To send the message via USB, the program creates a stream that uses SDL functions to open a connection with the USB driver. Then the transfer class starts a thread that will send each instruction in succession, respecting the set deadlines.
Here is the code:
OutputStream.hpp
#pragma once
#include <cstdint>
#include <vector>
class OutputStream {
public:
virtual ~OutputStream() = default;
virtual bool open() = 0;
virtual void close() = 0;
// Envoi dâun buffer brut
virtual bool send(const uint8_t* data, size_t size) = 0;
bool send(const std::vector<uint8_t>& data) {
return send(data.data(), data.size());
}
};
OutputStreamUSB.hpp
#pragma once
#include "OutputStream.hpp"
#include <SDL3/SDL_hidapi.h>
class OutputStreamUSB : public OutputStream {
private:
SDL_hid_device* device = nullptr;
unsigned short vendor_id;
unsigned short product_id;
public:
OutputStreamUSB(unsigned short vid, unsigned short pid)
: vendor_id(vid), product_id(pid) {}
bool open() override {
if (SDL_hid_init() != 0)
return false;
device = SDL_hid_open(vendor_id, product_id, nullptr);
return device != nullptr;
}
void close() override {
if (device) {
SDL_hid_close(device);
device = nullptr;
}
SDL_hid_exit();
}
bool send(const uint8_t* data, size_t size) override {
if (!device) return false;
int result = SDL_hid_write(device, data, size);
return result >= 0;
}
};
TransfertFile.hpp
#pragma once
#include <SDL3/SDL.h>
#include <format>
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <thread>
#include <atomic>
// --- Classe de base TransfertFile ---
class TransfertFile {
protected:
std::thread worker;
std::atomic<bool> running{false};
std::vector<MidiCommand> commands;
void postEvent(Uint32 type, int current, int total) {
SDL_Event event;
SDL_zero(event);
event.type = type;
event.user.code = current; // Utilisation de code pour l'index
event.user.data1 = (void*)(uintptr_t)total; // Utilisation de data1 pour le total
SDL_PushEvent(&event);
}
public:
virtual ~TransfertFile() {
stop();
}
virtual bool loadFile(const std::string& path) = 0;
void start() {
if (running) return;
running = true;
worker = std::thread(&TransfertFile::run, this);
}
void stop() {
running = false;
if (worker.joinable()) worker.join();
}
virtual void run() = 0;
};
TransfertMidiOx.hpp
#pragma once
#include "TransfertFile.hpp"
#include "OutputStream.hpp"
// --- Définition des événements personnalisés ---
Uint32 FILE_TRANSFERT_IN_PROGRESS = 0;
Uint32 FILE_TRANSFERT_FINISH = 0;
Uint32 FILE_TRANSFERT_FAILED = 0;
struct MidiCommand {
uint32_t timestamp; // en ms
std::string data; // Le reste de la ligne
};
// --- Classe dérivée TransfertMidiOx ---
class TransfertMidiOx : public TransfertFile {
private:
OutputStream& stream;
public:
TransfertMidiOx(OutputStream &s) : stream(s) {}
bool loadFile(const std::string& path) override {
std::ifstream file(path);
if (!file.is_open()) return false;
std::string line;
while (std::getline(file, line)) {
if (line.empty()) continue;
std::stringstream ss(line);
std::string hexTime;
ss >> hexTime; // Lit les 8 premiers caractĂšres hex
try {
uint32_t ts = std::stoul(hexTime, nullptr, 16);
std::string rest;
std::getline(ss, rest);
commands.push_back({ts, rest});
} catch (...) { continue; }
}
return !commands.empty();
}
void run() override {
if (commands.empty()) return;
uint64_t startTime = SDL_GetTicks();
int total = static_cast<int>(commands.size());
for (int i = 0; i < total; ++i) {
if (!running) break;
uint64_t now = SDL_GetTicks() - startTime;
if (now < commands[i].timestamp) {
SDL_Delay(commands[i].timestamp - (uint32_t)now);
}
std::vector<uint8_t> packet = parseMidiLine(commands[i]);
if (!stream.send(packet)) {
postEvent(FILE_TRANSFERT_FAILED, i, total);
stream.close();
return;
}
postEvent(FILE_TRANSFERT_IN_PROGRESS, i + 1, total);
}
if (running) postEvent(FILE_TRANSFERT_FINISH, total, total);
running = false;
}
std::vector<uint8_t> parseMidiLine(const MidiCommand& cmd) {
std::stringstream ss(cmd.data);
std::string byteStr;
std::vector<uint8_t> result;
for (int i = 0; i < 3 && ss >> byteStr; ++i) {
try {
result.push_back((uint8_t)std::stoul(byteStr, nullptr, 16));
} catch (...) {
break;
}
}
return result;
}
};
Main.cpp
#include <SDL3/SDL.h>
#include <format>
#include <iostream>
#include <string>
#include <map>
#include <memory>
#include "OutputStreamUSB.hpp"
#include "TransfertMidiOx.hpp"
constexpr int screenWidth{1024};
constexpr int screenHeight{768};
const char* screenTitle = "Midi-Ox transmitter";
SDL_Window* window{nullptr};
SDL_Renderer* renderer{nullptr};
std::unique_ptr<TransfertMidiOx> transmitter;
std::string midiFile = "";
int USBVendorID = 0;
int USBProductID = 0;
int progress = 0;
int failed_message = 0;
void listUSBDevices() {
if (SDL_hid_init() != 0) {
std::cerr << "HID init failed\n";
return;
}
SDL_hid_device_info* devs = SDL_hid_enumerate(0x0, 0x0);
SDL_hid_device_info* cur = devs;
while (cur)
{
std::cout << "Device found:\n";
std::cout << " VID: 0x" << std::hex << cur->vendor_id << "\n";
std::cout << " PID: 0x" << std::hex << cur->product_id << "\n";
if (cur->manufacturer_string)
std::wcout << L" Manufacturer: " << cur->manufacturer_string << L"\n";
if (cur->product_string)
std::wcout << L" Product: " << cur->product_string << L"\n";
std::cout << "-----------------------\n";
cur = cur->next;
}
SDL_hid_free_enumeration(devs);
SDL_hid_exit();
}
bool init() {
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) return false;
// Enregistrement des événements
FILE_TRANSFERT_IN_PROGRESS = SDL_RegisterEvents(3);
FILE_TRANSFERT_FINISH = FILE_TRANSFERT_IN_PROGRESS + 1;
FILE_TRANSFERT_FAILED = FILE_TRANSFERT_IN_PROGRESS + 2;
if (!SDL_CreateWindowAndRenderer(screenTitle, screenWidth, screenHeight, SDL_WINDOW_RESIZABLE, &window, &renderer)) {
return false;
}
SDL_SetRenderVSync(renderer, 1);
return true;
}
bool start() {
auto usb = std::make_unique<OutputStreamUSB>(USBVendorID, USBProductID);
transmitter = std::make_unique<TransfertMidiOx>(*usb);
if (transmitter->loadFile(midiFile)) {
transmitter->start();
return true;
}
return false;
}
void loop() {
bool quit{false};
SDL_Event event;
while (!quit) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
quit = true;
}
else if (event.type == FILE_TRANSFERT_IN_PROGRESS) {
int current = event.user.code;
int total = (int)(uintptr_t)event.user.data1;
progress = (current * 100) / total;
}
else if (event.type == FILE_TRANSFERT_FINISH) {
progress = 100;
}
else if (event.type == FILE_TRANSFERT_FAILED) {
failed_message = event.user.code;
}
}
// --- Rendu ---
SDL_SetRenderDrawColor(renderer, 20, 20, 25, 255);
SDL_RenderClear(renderer);
if (failed_message != 0) {
SDL_SetRenderDrawColor(renderer, 255, 50, 50, 255);
SDL_RenderDebugTextFormat(renderer, 10, 80, "Transmission failed at msg %d", failed_message);
} else {
if (progress < 100) {
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderDebugTextFormat(renderer, 10, 80, "Transmission: %d%%", progress);
} else {
SDL_SetRenderDrawColor(renderer, 50, 255, 50, 255);
SDL_RenderDebugText(renderer, 10, 80, "Transmission finished!");
}
}
SDL_RenderPresent(renderer);
}
}
void shutdown() {
if (transmitter) transmitter->stop();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
int main(int argc, char* argv[]) {
if (argc <= 1) {
std::cerr << "Usage:\n"
<< " ./midi-ox-transmitter --file <filename> --vid <hexadecimal integer> --pid <hexadecimal integer>\n"
<< " ./midi-ox-transmitter --listUSB\n";
return 1;
}
for (int i = 1; i < argc; ++i) {
if (SDL_strcmp(argv[i], "--file") == 0) {
if (i + 1 >= argc) {
std::cerr << "Error: --file requires a filename\n";
return 1;
}
midiFile = argv[++i];
} else if (SDL_strcmp(argv[i], "--vid") == 0) {
if (i + 1 >= argc) {
std::cerr << "Error: --vid requires a hexadecimal integer\n";
return 1;
}
size_t idx = 0;
USBVendorID = std::stoul(argv[++i], &idx, 16);
return 0;
} else if (SDL_strcmp(argv[i], "--pid") == 0) {
if (i + 1 >= argc) {
std::cerr << "Error: --pid requires a hexadecimal integer\n";
return 1;
}
size_t idx = 0;
USBProductID = std::stoul(argv[++i], &idx, 16);
return 0;
} else if (SDL_strcmp(argv[i], "--listUSB") == 0) {
listUSBDevices();
return 0;
} else {
std::cerr << "Unknown argument: " << argv[i] << "\n";
return 1;
}
}
if (midiFile.empty()) {
std::cerr << "No MIDI file specified.\n";
return 1;
}
if (init()) {
start();
loop();
}
shutdown();
return 0;
}
Use the command â./midi-ox-transmitter --listâ to scan for connected devices.
Use the command â./midi-ox-transmitter --file my_instructions.txt --vid 0x1234 --pid 0x4568â to send your file.
I hope this solves your problem.