Windows MIDI interface

I’m looking for a MIDI (Musical Instrument Digital Interface) interface for SWI Prolog. I found one for Mac (plmidi.c), but I 'm running Windows. I also found some MIDI file parsers in Prolog, but I need MIDI messaging and handling.

I don’t need a full implementation of the Windows MIDI interface. I only need to be able to send and receive

  • Note On,
  • Note Off,
  • Program Change, and
  • Control Change messages.

Sending and receiving System Exclusive messages is a bonus.

Note that I’m a musician, not a professional programmer.

Thanks in advance for any help.

Welcome to the forum!

I did a search also and only found what you noted, which is from the SWI-Prolog packages.

While others may have better answers, here is something to get you started if nothing better comes along.

Using ChatGPT and the Mac Code asked ChatGPT about converting the Mac specific headers for Windows as a litmus test.

(Click triangle to expand)

ChatGPT August 3 Version
GPT-3.5
Prompt

Convert following C code for Mac to Windows.

```c
#include <mach/mach_time.h>
#include <mach/mach_port.h>
#include <mach/mach_interface.h>
#include <mach/mach_init.h>
#include <CoreMIDI/MIDIServices.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/IOMessage.h>
```

Completion

The provided code seems to include headers from macOS-specific frameworks (mach and CoreMIDI) and also includes headers related to IOKit, which is again macOS-specific. These libraries are not available on Windows. Converting this code to Windows would require using equivalent libraries and headers available on the Windows platform.

If you are trying to achieve similar functionality on Windows, you would need to use the appropriate Windows APIs and libraries. For example, for timing-related functionality, you might use the Windows High-Resolution Timer APIs. For MIDI functionality, you would use the Windows Multimedia APIs.

Here’s a rough guide on what you might need to do:

  1. Timing Related Code (mach/mach_time.h):
    On Windows, you can use the QueryPerformanceCounter and QueryPerformanceFrequency functions to achieve similar high-resolution timing functionality. You’ll need to include windows.h to access these functions.

  2. MIDI Code (CoreMIDI/MIDIServices.h):
    Windows has its own MIDI API called the Windows Multimedia MIDI API. You would need to use functions like midiInOpen, midiInStart, midiOutOpen, midiOutShortMsg, etc., which are part of the Windows Multimedia API. To use these, you’ll need to include windows.h and link against the winmm.lib library.

  3. IOKit Code (IOKit/pwr_mgt/IOPMLib.h, IOKit/IOMessage.h):
    There is no direct equivalent to IOKit on Windows. Power management functionality might involve using Windows Power Management APIs, and handling I/O messages might involve using Windows Messaging and WindowProc procedures.

Please note that the actual conversion can be quite complex and might involve rewriting significant portions of the code. It’s also important to understand the differences in concepts and APIs between macOS and Windows.

Here’s a general template of what the converted code might look like, although this is a simplified example and you would need to adapt it to your specific use case:

#include <windows.h>
#include <mmsystem.h> // for MIDI functions (winmm.lib)

// Timing related code
LARGE_INTEGER frequency;
LARGE_INTEGER startCounter;

void StartTimer() {
    QueryPerformanceFrequency(&frequency);
    QueryPerformanceCounter(&startCounter);
}

double GetElapsedTime() {
    LARGE_INTEGER currentCounter;
    QueryPerformanceCounter(&currentCounter);
    return (double)(currentCounter.QuadPart - startCounter.QuadPart) / frequency.QuadPart;
}

// MIDI related code
HMIDIIN midiIn;
HMIDIOUT midiOut;

void InitializeMIDI() {
    midiInOpen(&midiIn, ...);
    midiOutOpen(&midiOut, ...);
}

void SendMIDIMessage(DWORD message) {
    midiOutShortMsg(midiOut, message);
}

int main() {
    StartTimer();
    InitializeMIDI();

    // Your code here

    double elapsed = GetElapsedTime();

    // Clean up MIDI
    midiOutClose(midiOut);
    midiInClose(midiIn);

    return 0;
}

Please note that this is a very simplified example, and the actual code and functions you need to use will depend on your specific requirements. Additionally, error handling and more detailed setup might be necessary.

Obviously much more work is needed but it seems that using ChatGPT to assist in converting the Mac code is a possibility.

Also checked if the Windows MIDI library is a hallucination, it is not.

Roughly, I guess there are two options. One is to port the package to Windows. This probably is asking a bit too much if you have no experience with C :frowning: The other is to use the new Python interface that should work out of the box if you install the SWI-Prolog development versions (9.1.14 as we speak) and the Windows Python binary. I’d expect Python to have a MIDI interface and making the call from Prolog to Python is most likely close to trivial. Let us know how it goes!

1 Like

I let ChatGPT loose on this, here is what I tried.

Note: The reason the code is in a hidden section is because search engines will not index the content in a hidden section. As I have not tested this code don’t want it to be part of a training set.

(Click triangle to expand)

ChatGPT August 3 Version
GPT-3.5
Prompt

SWI-Prolog can call Python using the Janus library, here are some examples

```prolog
% call a built-in
?- py_call(print("Hello World!\n")).
true.
% call function in a module
?- py_call(sys:getsizeof([1,2,3]), Size).
Size = 80.
% call function on an attribute of a module
?- py_call(sys:path:append("/home/bob/janus")).
true
% get attribute from a module
?- py_call(sys:path, Path)
Path = ["dir1", "dir2", ...]
```

Create SWI-Prolog code for the following MIDI commands using the Janus library

* Note On
* Note Off
* Program Change
* Control Change messages

Completion

Janus is a Prolog interface to various scripting languages, including Python. To use the Janus library to send MIDI commands, you’ll need to have both SWI-Prolog and Janus installed, as well as a Python MIDI library like mido to interact with MIDI devices.

Here’s how you could create SWI-Prolog code using the Janus library to send MIDI commands using the mido Python library:

:- use_module(library(janus)).
:- use_module(library(mido)).

% Load the Janus Python module
?- py_load_module('mido', [send/1, open_output/2]).

% Open a MIDI output port
open_midi_port(Port) :-
    py_call(mido:open_output, [Port, []]).

% Send a Note On MIDI command
note_on(Port, Note, Velocity) :-
    py_call(mido:send, [Port, 'note_on', note=Note, velocity=Velocity]).

% Send a Note Off MIDI command
note_off(Port, Note) :-
    py_call(mido:send, [Port, 'note_off', note=Note, velocity=0]).

% Send a Program Change MIDI command
program_change(Port, Program) :-
    py_call(mido:send, [Port, 'program_change', program=Program]).

% Send a Control Change MIDI command
control_change(Port, Control, Value) :-
    py_call(mido:send, [Port, 'control_change', control=Control, value=Value]).

% Close the MIDI port
close_midi_port(Port) :-
    py_call(Port:close).

% Example usage
:- open_midi_port(Port),
   note_on(Port, 60, 100),          % Note On (MIDI note 60, velocity 100)
   sleep(1),                         % Wait for a second
   note_off(Port, 60),              % Note Off (MIDI note 60)
   program_change(Port, 10),        % Program Change (select program 10)
   control_change(Port, 7, 100),    % Control Change (controller 7, value 100)
   sleep(1),                         % Wait for a second
   close_midi_port(Port).

In this code, you first load the mido Python module using py_load_module/2. Then, you define Prolog predicates for sending different MIDI commands using the mido library’s functions. The example usage demonstrates how to open a MIDI port, send various MIDI commands, and close the MIDI port when done.

Remember that you need to have both SWI-Prolog and the mido Python library installed for this code to work. Additionally, you might need to adjust the MIDI port name or other parameters to match your MIDI setup.

The SWI-Prolog Janus library

https://www.swi-prolog.org/pldoc/man?section=janus

Checked that the Python MIDI library exist

https://mido.readthedocs.io/en/stable/


I guess one could test this without a MIDI device using the computer to simulate a MIDI device but I have not used MIDI in decades.

Curious to know how close the code is to working.

1 Like

Not very good answer from our beloved friend :slight_smile: Translating some of the calls from the mido docs, Janus interface would use

pip install mido

Now in SWI-Prolog:

?- py_call(mido:'Message'('note_on', note=60), Msg).
Msg = <py_Message>(000001e5564c16d0).
?- py_call($Msg:bytes(), Bytes).
Bytes = [144, 60, 64],
Msg = <py_Message>(000001e5564c16d0).

Somehow the copy example does not work.

I was amazed it could even do this considering that the Janus library was not part of the training set.

I am not getting what you mean here. :thinking:

51 ?- py_call(mido:'Message'('note_on', note=60), Msg).
Msg = <py_Message>(0x7f2658c12050).

52 ?- py_call($Msg:copy(channel=2), Copy).
ERROR: Python 'SystemError':
ERROR:   ../Objects/tupleobject.c:964: bad argument to internal function
ERROR: In:
ERROR:   [10] janus:py_call(<py_Message>(0x7f2658c12050):copy(...),_144)
ERROR:    [9] toplevel_call('<garbage_collected>') at /home/janw/src/swipl-devel/build.pgo/home/boot/toplevel.pl:1173
1 Like

Pushed a fix for that. Should be in tomorrows daily build from Download daily builds for Windows. Check the date as the build sometimes fails :frowning:

1 Like

Thanks.

Glad that was found sooner rather than later. :+1:

Downloaded, installed and ran the Windows build of the day.

The copy command as noted here is now working.

(Click triangle to expand)
Welcome to SWI-Prolog (threaded, 64 bits, version 9.1.14-41-g41ac4a569)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- py_version.
% Janus embeds Python 3.11.4 (tags/v3.11.4:d2340ef, Jun  7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)]
true.

?- py_call(print("Hello World!\n")).
Hello World!

true.

?-  py_call(mido:'Message'('note_on', note=60), Msg).
Msg = <py_Message>(000001e8f3a73110).

?- py_call($Msg:bytes(), Bytes).
Bytes = [144, 60, 64],
Msg = <py_Message>(000001e8f3a73110).

?- py_call($Msg:copy(channel=2), Copy).
Copy = <py_Message>(000001e8f4589390),
Msg = <py_Message>(000001e8f3a73110).

Thanks for all the amazingly useful help.

At home I’m working on a gapped desktop PC. I need software that can be installed from a USB stick.

I’ve tried to install mido in the past, but I believe I got errors during installation. Anyway, it didn’t work as expected. I used the command

py -m pip install "C:\....\mido-.....tar.gz"

I will try again tonight with a newer version of mido.

If that works I will look into the janus (Prolog-Python) interface.

FYI

When I tried this again today could not get the code to work due to my lack of knowledge of many of the details that are not part of my repertuar.

The first problem was

ERROR:    Python 'ModuleNotFoundError':
ERROR:      No module named 'rtmidi'

For which this seemed to resolve

pip install python-rtmidi

The next issue was trying to understand how to connect the MIDI player in the Windows media player. Seems the name ChatGPT gave me was not valid.

As I was three layers deep in open projects with this being the third my computer was grinding to a crawl so dumped this task for latter, which could be days to never before getting back too.

Also I know that Jan W. was using a copy in his code but I don’t know why. I would have expected that this

py_call(mido:send, [Port, 'note_on', note=Note, velocity=Velocity]).

would play a note once the port was open.

I would expect that as well, as long as everything is initialised/instantiated. Does it work in Python?

ATM I’m using Autohotkey and JavaScript for their MIDI interfaces. Autohotkey simply calls DLLs, although the 64-bit version is not as complete as the old 32-bit implementation. Javascript has a complete MIDI implementation and there exist abstraction layers making it very easy to handle MIDI messages.

Have not tried a pure Python test.

I did look for a few simple ones but did not find one.
Do you know of an example for Python doing MIDI on Windows from an interactive Python top-level I can try, e.g.

starting from this point and only using the command line

Microsoft Windows [Version 10.0.19045.3324]
(c) Microsoft Corporation. All rights reserved.

C:\Users\Groot>python
Python 3.11.4 (tags/v3.11.4:d2340ef, Jun  7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

and I will give it a try.

Note that we also have the WASM version of SWI-Prolog that can run in your browser or under node.js. As a Prolog system it is a bit crippled though. 32-bits, no threads and not much of a development environment.

Probably must be like this. Prolog compound terms are used to denote both the function name and the arguments. Note that SWI-Prolog supports f() as valid syntax for functions that take no arguments. Using Module:Atom, e.g., sys:path, you access properties of an object.

py_call(mido:send(Port, 'note_on', note=Note, velocity=Velocity)).

Hello, I’m the author of plmidi :slight_smile: You may have already solved your problem to your own satisfication, but if it’s of any interest, I wrote it quite a long time ago and I chose to go for the low level Mac CoreMIDI toolkit because (a) it cuts out all the middle-men and so is likely to get the best latency possible on a Mac, and (b) because it behaves nicely when you schedule a MIDI event to play at a particular time in the future - the Mac MIDI system includes an accurate scheduler so you don’t have to write your own timing loops.

I’ve also used mido quite a lot in Python and yes, it’s quite good once you get past setting up rtmidi properly. But if I remember correctly, I had to write my own timing code (based on a sleep_until function) but it was never very accurate.

For a cross-platform basis, you could take a look at portmidi [GitHub - PortMidi/portmidi: portmidi is a cross-platform MIDI input/output library]. It seems well maintained. I don’t know if handles event scheduling though.

Another possibility for cross-platform use that just occured to me is to go via Java. Again, I used the Java MIDI system a long time ago but I have never tried calling Java from SWI Prolog.

1 Like

If you use it from Prolog you can use Prolog for the timing?

My current situation is that I first need to install the dependencies for the mido package on my gapped PC. This will probably take some time (I guess weeks) for me to understand and execute. Here is the detailed help I got from the mido developer : Offline installation · mido · Discussion #544 · GitHub and from the Python forum : Mido installation fails - #7 by Remifa2 - Python Help - Discussions on Python.org .

I prefer this slow Python ‘snake’ road because it will also solve a similar mido problem that I have with the BespokeSynth VST on the same PC.

My next concern will be efficiency as the PC I use is already heavy loaded when I’m running a DAW with several (sometimes 16) VSTs and real time pitch detection from a micro audio signal.

I hope Prolog will, with its unmatched “pattern matching”, help me to recognise higher level musical structures.

Note that I’m away until Tuesday next week, but please feel free to continue this interesting discussion related to MIDI, music and Prolog. I’m all ears.

If you have Java installed on your PC then you have access, via SWIPL’s bundled JPL, to all the good stuff in javax.sound, including its MIDI package

https://docs.oracle.com/javase/tutorial/sound/overview-MIDI.html

and this demo should just work:

jpl_midi_demo :-
	jpl_midi_demo(20).	% play 20 random notes

jpl_midi_demo(N) :-
	jpl_call('javax.sound.midi.MidiSystem', getSynthesizer, [], Synth),
	jpl_call(Synth, open, [], _),
	jpl_call(Synth, getChannels, [], Channels),
	jpl_get(Channels, 0, Channel0),	% i.e. Channel0 = Channels[0]
	jpl_midi_demo(N, Channel0),
	jpl_call(Synth, close, [], _).

jpl_midi_demo(N, Channel) :-
	(	N @> 0
	->	Note is 50+random(50),	% see MIDI docs for pitch relationship
		Velocity is 100,		% arbitrary value > 0
		jpl_call(Channel, noteOn, [Note,Velocity], _),
		sleep(0.5),	% play note for approx 0.5 seconds
		jpl_call(Channel, noteOff, [Note], _),
		Nx is N-1,
		jpl_midi_demo(Nx, Channel)	% play remaining notes
	;	true
	).

This plays random notes through your PC’s sound card using a software synthesizer from within the Java library; I guess you want (and need?) to communicate with an external synthesizer? I assume the package supports this, have no experience, am not recommending this route for you, but this package is mature, dependency-free and cross-platform.

1 Like