Ok. Progress. For this to work you need the latest version of SWI-Prolog, which you can download from Download daily builds for Windows tomorrow (should be dated Oct 3).
It turns out that mido.open_input() creates a Python thread and there were several issues dealing with Python threads. At least what is needed for this demo is now resolved
Now, I use this Python code, saved as input.py
:
import janus_swi as janus
import mido
engine = None
def on_message(message): # callback function
global engine
if not engine:
engine = janus.attach_engine()
print(f"Created Prolog engine {engine}")
janus.once("midi_message(Msg)",
{'Msg':{'type':message.type,
'time':message.time,
'channel':message.channel,
'note':message.note,
'velocity':message.velocity
}})
def open(port):
mido.open_input(port, callback=on_message)
And this Prolog code, saved as inputs.pl
:
:- use_module(library(janus)).
:- py_add_lib_dir(.).
go :-
py_call(input:open('MIDI Out:out 129:0')).
:- public
midi_message/1.
midi_message(Msg) :-
writeln(Msg).
Now, we load input.pl
into Prolog and call
?- go.
This calls input:open()
with the MIDI port. This load input.py
and runs the open()
function that opens the MIDI input and creates a Python thread that calls on_message
on each callback. First thing we do is to associate a Prolog engine with this thread. We can also omit that, but then each callback will create and destroy a Prolog engine, which could be a bit slow.
Next, we can use the keyboard and we see
10 ?- go.
true.
11 ?- Created Prolog engine 3
py{channel:0,note:44,time:0,type:note_on,velocity:100}
py{channel:0,note:44,time:0,type:note_off,velocity:0}
py{channel:0,note:36,time:0,type:note_on,velocity:100}
py{channel:0,note:36,time:0,type:note_off,velocity:0}
py{channel:0,note:46,time:0,type:note_on,velocity:100}
py{channel:0,note:46,time:0,type:note_off,velocity:0}
py{channel:0,note:40,time:0,type:note_on,velocity:100}
py{channel:0,note:40,time:0,type:note_off,velocity:0}
py{channel:0,note:55,time:0,type:note_on,velocity:100}
py{channel:0,note:55,time:0,type:note_off,velocity:0}
Note that I create a Python dict from the message with the information I want. This gets mapped to a Prolog dict rather than a Prolog reference to the Python object. You can also extract the information at the Prolog site, but this is probably simpler and faster.
Note that the callbacks happen in a different Prolog thread (thread 3). You can probably make a second open() call, which will make another thread listening to a different MIDI port. You now need to link the stuff together. There are many ways to do that. Assert the messages in the database, using e.g.
midi_message(py{channel:Channel,note:Note,time:Time,type:Type,velocity:Velocity}) :-
assertz(midi_message(Channel, Note, Time, Type, Velocity)).
Then use thread_wait/2 to wait for events and act on them? That is just one of the many ways. Curious to hear your story 