package hardware import ( "context" "fmt" "regexp" "strconv" "strings" "github.com/mattrtaylor/go-rtmidi" "github.com/rs/zerolog/log" ) // MIDIDriver represents how the protocol is defined type MIDIDriver struct { peripherals map[string]Peripheral // The list of peripherals } // NewMIDIDriver creates a new DMXUSB protocol func NewMIDIDriver() *MIDIDriver { log.Trace().Str("file", "MIDIDriver").Msg("MIDI driver created") return &MIDIDriver{ peripherals: make(map[string]Peripheral), } } // Initialize initializes the MIDI driver func (d *MIDIDriver) Initialize() error { log.Trace().Str("file", "MIDIDriver").Msg("MIDI driver initialized") return nil } // GetName returns the name of the driver func (d *MIDIDriver) GetName() string { return "MIDI" } // GetPeripheral gets the peripheral that correspond to the specified ID func (d *MIDIDriver) GetPeripheral(peripheralID string) (Peripheral, bool) { // Return the specified peripheral peripheral, found := d.peripherals[peripheralID] if !found { log.Error().Str("file", "MIDIDriver").Str("peripheralID", peripheralID).Msg("unable to get this peripheral in the MIDI driver") return nil, false } log.Trace().Str("file", "MIDIDriver").Str("peripheralID", peripheralID).Msg("MIDI peripheral found in the driver") return peripheral, true } func splitStringAndNumber(input string) (string, int, error) { // Regular expression to match the text part and the number at the end re := regexp.MustCompile(`^(.*?)(\d+)$`) matches := re.FindStringSubmatch(input) // Check if the regex found both a text part and a number if len(matches) == 3 { // matches[1]: text part (might contain trailing spaces) // matches[2]: numeric part as a string textPart := strings.TrimSpace(matches[1]) // Remove any trailing spaces from the text numberPart, err := strconv.Atoi(matches[2]) if err != nil { return "", 0, err // Return error if the number conversion fails } return textPart, numberPart, nil } // Return an error if no trailing number is found return "", 0, fmt.Errorf("no number found at the end of the string") } // Scan scans the interfaces compatible with the MIDI protocol func (d *MIDIDriver) Scan(ctx context.Context) error { midiPeripherals := make(map[string]Peripheral) log.Trace().Str("file", "MIDIDriver").Msg("opening MIDI scanner port...") midiScanner, err := rtmidi.NewMIDIInDefault() if err != nil { log.Err(err).Str("file", "MIDIDriver").Msg("unable to open the MIDI scanner port...") return fmt.Errorf("unable to open the MIDI scanner: %s", err) } defer midiScanner.Close() midiScanner.SetCallback(func(m rtmidi.MIDIIn, b []byte, f float64) { }) log.Trace().Str("file", "MIDIDriver").Msg("scanning MIDI peripherals...") devicesCount, err := midiScanner.PortCount() if err != nil { log.Err(err).Str("file", "MIDIDriver").Msg("unable to scan MIDI peripherals...") return fmt.Errorf("unable to scan MIDI peripherals: %s", err) } for i := 0; i < devicesCount; i++ { portName, err := midiScanner.PortName(i) if err != nil { log.Warn().Str("file", "MIDIPeripheral").Msg("found peripheral without a correct name, set it to unknown") portName = "Unknown device 0" } // Separate data name, location, err := splitStringAndNumber(portName) if err != nil { log.Err(err).Str("file", "MIDIDriver").Str("description", portName).Msg("invalid peripheral description") return fmt.Errorf("invalid pripheral description: %s", err) } log.Info().Str("file", "MIDIDriver").Str("name", name).Int("location", location).Msg("MIDI peripheral found") // Add the peripheral to the temporary list sn := strings.ToLower(strings.Replace(name, " ", "_", -1)) midiPeripherals[sn] = NewMIDIPeripheral(name, location, sn) } // Compare with the current peripherals to detect arrivals/removals removedList, addedList := comparePeripherals(d.peripherals, midiPeripherals) // Emit the events emitPeripheralsEvents(ctx, removedList, PeripheralRemoval) log.Info().Str("file", "MIDIDriver").Msg("MIDI remove list emitted to the front") emitPeripheralsEvents(ctx, addedList, PeripheralArrival) log.Info().Str("file", "MIDIDriver").Msg("MIDI add list emitted to the front") // Store the new peripherals list d.peripherals = midiPeripherals return nil } // CreatePeripheral is not implemented here func (d *MIDIDriver) CreatePeripheral(context.Context) (Peripheral, error) { return nil, nil } // RemovePeripheral is not implemented here func (d *MIDIDriver) RemovePeripheral(serialNumber string) error { return nil }