Windows MIDI interface

Your explanation of alarm/4 explains a lot.

What you mean is pulse or beat (metronome, tempo in bpm), not Rhythm (which is the pattern of durations of notes/sounds/accents and rests). The problem with a fixed tempo is that it sounds artificial, because good musicians accelerate and slow down to express a musical phrase. This is currently an important (research) issue with computer music.

I’ve a new problem with MIDI input coming from some programs.

One example is FractMus 2000 ( FractMus ), a program that generates fractal music. I selected this program as it is much easier to install than the big software program I use on a daily basis. For this little program to work properly you need to install Coolsoft software including soundfonts. I’m willing to search for another program on my PC if I’m asking too much.

The following Prolog program doesn’t work as expected as soon as FractMus is running and the output port selected for FractMus is the same as the input port selected in the Prolog program. I expect it to wait for a Note On event to trigger something (writeln/1), but nothing happens.

:- use_module(library(janus)).

:- dynamic
	 inport/1.
	
open_in_port :-
	py_call(mido:open_input('loopMIDI Port 1 1'), InPort),	% substitute for your own input port name
	asserta(inport(InPort)),
	thread_create(input_from(InPort), _).

close_in_port :-
	(retract(inport(InPort))
	-> 	py_call(InPort:close())
	;	true
	).
	
input_from(Port) :-
	forall(repeat, receive_one(Port)).
	
receive_one(Port) :-
	py_call(Port:receive(), Msg),
	py_call(Msg:type, Type),
	handle_midi_message(Type, Msg),
	py_free(Msg).

handle_midi_message('note_on', _) :-
	writeln(handle_midi_message('note_on')),
	close_in_port.

:- open_in_port.

I tested this with a Python (mido) script and it works well with FractMus. FractMus also works with MIDI Tools and VSTs in a simple DAW like LMMS or standalone synths like Surge XT. It also works with non-Prolog scripts I wrote some time ago.

Here is the Python script that works fine :

def print_message(message):  # callback function
    print(message)

import mido

mido.open_input('loopMIDI Port 1 1', callback=print_message)

import time 

while True: # keep the script running
   print(".")
   time.sleep(60) # sleep for 1 minute

I found another program (with a virtual keyboard) that generates note events that aren’t detected by the Prolog script : the standalone version of Dexed ( Releases · asb2m10/dexed · GitHub ). Dexed is a popular FM synthesiser based on Yamaha’s DX-7. The MIDI out port can be set by clicking on the options button in the top left corner of the window. This software can be run when unzipped and doesn’t need to be installed.

Correction: contrary to what I observed before, Dexed cooperates fine with Prolog.

Can you provide the exact steps to reproduce this, with the Prolog and Python scripts involved? This MIDI stuff is all pretty involved. I’m interested in possible bugs in the Janus interface, not in installing and configuring MIDI components :slight_smile:

Please also check the latest daily build when you encounter problems. There have been a few fixes quite recently to Janus.

I’ve installed swipl-w64-2023-10-24.exe.

Unfortunately a program that generates MIDI messages that are not handled properly by Prolog will have to be installed to reproduce the problem. You can’t make an omelette without breaking some eggs. :wink: I assume you already installed MIDI Tools as it can be used to verify my observations. If you need more info about how to operate the latter program I’ll be glad to help.

Here are the steps to reproduce the problem with Fractal Music Generator (abbreviated to FMG) which is easier to install than FractMus :-

  1. Download FMG ( Fractal Music Generator by BetaZeta ); you can select the smaller java version instead of the Windows version if you prefer
  2. Unzip and Install FMG (admin permissions aren’t necessary)
  3. Run FMG
  4. Select the Devices tab in FMG
  5. Click on the (output) port you want to listen to in my Prolog script
  6. Run the Prolog script in my previous post (and for your convenience repeated hereafter) after setting the input port to the one selected in FMG
  7. Select the Fractal tab in FMG
  8. Enable Melody in FMG (the button should be light grey)
  9. Click on the fractal graphic.

You should hear some sounds that stop after a few seconds.

Here is my Prolog script again :

:- use_module(library(janus)).

:- dynamic
	 inport/1.
	
open_in_port :-
	py_call(mido:open_input('loopMIDI Port 1 1'), InPort),	% substitute for your own input port name
	asserta(inport(InPort)),
	thread_create(input_from(InPort), _).

close_in_port :-
	(retract(inport(InPort))
	-> 	py_call(InPort:close())
	;	true
	).
	
input_from(Port) :-
	forall(repeat, receive_one(Port)).
	
receive_one(Port) :-
	py_call(Port:receive(), Msg),
	py_call(Msg:type, Type),
	handle_midi_message(Type, Msg),
	py_free(Msg).

handle_midi_message('note_on', _) :-
	writeln(handle_midi_message('note_on')),
	close_in_port.

:- open_in_port.

The problem is that the note on events aren’t observed by the Prolog script. I expect it to write “handle_midi_message(‘note_on’)” on the console. All further input seems to be blocked after sending a first note. This might mean that close_in_port/1 has been called.

The messages sent by FMG are received by MIDI tools (in its MIDI input messages tool). This is proof that the messages are sent.

If you restart the Prolog script and send a note with the MIDI Tools keyboard, the Prolog script works properly. It writes a “handle_midi_message(‘note_on’)” message on the console.

The problem is not caused by mido, nor Python as the Python script published in my previous post works fine with FMG.

I hope this helps in identifying the cause of the problem. I appreciate your interest in making MIDI messaging a part of SWI Prolog.

Hmm. Installing the app works. It makes nice horrible noise (at least, how I used it). Indeed, Prolog doesn’t show any message on for me "loopMIDI Port 0", but neither does Python after

>>> import mido
>>> input = mido.open_input("loopMIDI Port 0")
>>> input.receive()

This just hangs. Also the MIDI tools report no activity. As long as the above hangs, it is not Prolog/Janus’ fault. Does the above work for you?

P.s. Why do you close the port after receiving?

Did you use my Python script?

def print_message(message):  # callback function
    print(message)

import mido

mido.open_input('loopMIDI Port 1 1', callback=print_message)

import time 

while True: # keep the script running
   print(".")
   time.sleep(60) # sleep for 1 minute

It’s important that you use the same port in both sending and receiving programs. In MIDI Tools you have to set the Input port in MIDI devices and then open the MIDI input messages window in order to view incoming messages. Be sure to enable Note input.

I close the port in Prolog because I just want to trigger a process in Prolog with the first note received. I think it doesn’t matter because something goes wrong with that first note coming from that particular software.
When you would use MIDI Tools’ keyboard to send note messages it will work fine.

Yes. That too doesn’t work and the pure Python script is reporting exactly what the Prolog+Janus+Python thing does. It prints the MIDI tools keyboard messages fine. Listening to the loopMIDI port, using the FractalMusic tool it does see messages as I select the device in the devices tab (control_change messages), but when playing a melody in the Fractal tab, there is sound, but otherwise no activity reported by any MIDI application.

That might be because the devices tab says “Internal Synthesizer”. There is a “Jack” button below, but that is disabled (rightfully as I do not have Jack installed). So, it looks like that you can select a MIDI device and it sends some messages around, but when playing a melody it doesn’t use MIDI.

With Fractal Music Generator I get lots of Note On messages in the beginning and lots of Note Off messages at the end of a melody with just one click on the image. It uses so many sequencers, creating a chaotic feeling, that it might overwhelm some systems. The tool produces complex conversions from graphics to MIDI data (melody or drum patterns) and is frequently used as MIDI input for DAWs. The Control Change messages you see are normal, but you can filter them with MIDI Tools.

I’m sure there must be something wrong in your setup.

In MIDI tools, the same port, now used as an input Port, must be opened in MIDI Tools. Note that the MIDI devices dialog box has two tabs : one for input and one for output ports. I forgot to tell that you need to click on the Record button in the MIDI input messages tool in order to view the received messages.

If I remember well, I downloaded the Windows zip file, not the java zip file.

FYI, Jack is for audio, not for MIDI. Are you sure you selected a MIDI port in FMG?

Here is some further info about the problem.

MidiEditor ( http://www.midieditor.org/ ) and MIDIBar ( part of MIDI-OX : http://www.midiox.com/ installation ), a small utility to play MIDI files, don’t work either. Both tools allow MIDI out port selection.

I suspected the burst of MIDI events of FMG would be the cause, but playing just one note, created in MidiEditor, doesn’t work either.

Here are a few other MIDI tools that work fine with Prolog : VSTHost ( VSTHost ), ToneSpace ( mucoder tonespace ) and Stochas ( https://stochas.org/ ).

I don’t know if it can help, but it’s a speculative thought I have for some time now: the cause of the problem might be the way ports are opened. I found a message circulating on the web about MIDI port problems : "The specified device is already in use. Wait until it is free, and then try again. " This however doesn’t explain why Python+mido and other tools aren’t and only Prolog is affected.

Note that FMG has a MIDI Dump facility that can be activated with the Dump button in the Devices tab. The dump appears in the small console in the Fractal tab.

Quite likely. I’m away from the machine I use for that for some days, so I can’t help right now. Something must be wrong though as even the MIDI port app shows a little window with the created ports and some statistics on these ports. Using the keyboard it nicely steps the number of midi messages, but when playing a melody, nothing happens there.

Thanks. That is worth a try try. Will be next week.

Possibly. Did you try the somewhat less nice callback based approach we got working earlier?

It works ! The cause of the problem was backtracking. When the first message received isn’t a Note On message, the input port is closed. Some software sends channel mode, control/program changes or other messages before sending notes.

My new Prolog script adds a clause “handle_midi_message(_, _).”, meaning that the predicate will always succeed.

:- use_module(library(janus)).

:- dynamic
	inport/1.
	
open_in_port :-
	py_call(mido:open_input('loopMIDI Port 1 1'), InPort),	% substitute for your own input port name
	asserta(inport(InPort)),
	thread_create(input_from(InPort), _).
	
close_in_port :-
	(retract(inport(InPort))
	-> 	py_call(InPort:close())
	;	true
	).
	
input_from(Port) :-
	forall(repeat, receive_one(Port)).
	
receive_one(Port) :-
	py_call(Port:receive(), Msg),
	py_call(Msg:type, Type),
	handle_midi_message(Type, Msg),
	py_free(Msg).

handle_midi_message('note_on', Msg) :-
	writeln(handle_midi_message('note_on', Msg)),
	close_in_port.	

handle_midi_message(_, _).

:- open_in_port.

@Jan:
Thanks for supporting me ! I hope you didn’t waste too much of your precious time on this issue.

Great. It always sounded rather unlikely that if something works in pure Python it would not work through Janus. The code can be a bit cleaner. Consider something like this:

:- use_module(library(janus)).

open_in_port :-
	py_call(mido:open_input('loopMIDI Port 1 1'), InPort),	% substitute for your own input port name
	thread_create(input_from(InPort), _).

input_from(Port) :-
	call_cleanup(
            forall(repeat, receive_one(Port)),
            py_call(Port:close())).

receive_one(Port) :-
	py_call(Port:receive(), Msg),
	py_call(Msg:type, Type),
	handle_midi_message(Type, Msg),
	py_free(Msg).

handle_midi_message('note_on', Msg) :-
	writeln(handle_midi_message('note_on', Msg)),
        !,
	fail.  % fail to get out of forall/2 loop.
handle_midi_message(_, _).

:- initialization open_in_port.

This doesn’t store the port handle globally (global data should always be a last resort). It places closing the port at a higher level of responsibility. call_cleanup/2 says “whenever and through whatever reason the first goal is completed (success, failure, error), call the second goal and propagate the success/failure/error of the first)”.

As a result, we must fail to in handle_midi_message/2 to make the forall/2 stop. A (recursive) loop may be cleaner.

Finally, initialization/1 is demanded by the standard to start goals by loading a file. It is also better as Prolog will complete the compilation of the file and run the goal afterwards.

Success.

Thanks for the ‘cleaned’ code. I’ve studied it and I have some questions.

  1. The following code crashes SWI Prolog :
open_out_port :- % substitute for your own output port name
	call_cleanup(
		py_call(mido:open_output('loopMIDI Port 2'), OutPort), 
		py_call(OutPort:close())).
  1. I can avoid using dynamically created facts (global data) for InPorts because the incoming messages contain a reference to the inPort. How do I refer to my outports when I need them without relying on “global data”?
py_call(mido:open_output('loopMIDI Port 2'), OutPort1),
py_call(mido:open_output('loopMIDI Port 1 3'), OutPort2),
py_call(mido:open_output('loopMIDI Port 2 4'), OutPort3),
  1. Sleep and alarm

Can this please be clarified as the following script shows a different behaviour :

start :-
	alarm(2, writeln('Alarm'), _, [remove(true)]),
	count_down(5).
	
count_down(-1).
count_down(Secs) :-
	writeln(Secs),
	sleep(1),
	SecsMinus1 is Secs - 1,
	count_down(SecsMinus1).

If alarm/4 can interrupt sleep/1 then I would expect ‘Alarm’ to be printed before the countdown ends, but it is printed after the countdown ends.

Thanks in advance for any help.

Runs fine for me. Doesn’t do anything useful though as it immediately closes the port. The template is this

setup_call_cleanup(
   OpenResource,
   UseResource,
   CloseResource).

That guarantees that if OpenResource succeeded and UseResource completes (success, failure, error), CloseResource is called.

I saved this code and loaded into the Linux version as well as both swipl-win.exe and swipl.exe. It works as promised:

3 ?- start.
5
4
Alarm
3
2
1
0
true .

So, what is your exact setup?

I took your example for my cleanup and here call_cleanup is used with two args. These predicates have different semantics. I’ll investigate this further.

input_from(Port) :-
	call_cleanup(
            forall(repeat, receive_one(Port)),
            py_call(Port:close())).

I’m using Windows 10 and a fairly recent (at most 2 weeks old) version of SWI Prolog. In my prolog environment alarm/4 doesn’t interrupt sleep/1. I consistently get

?- start.
5
4
3
2
1
0
Alarm
true .

I was wondering whether I need to use threads for my scenario, but your output clearly shows that something must be wrong with my setup. If you need to know more details about my setup, please let me know.

There is call_cleanup/2 and setup_call_cleanup/3. Roughly, the latter is defined as below, except that it ensures we cannot get an interrupt between the Setup and the call_cleanup/2, in which case the Cleanup would not be called.

 setup_call_cleanup(Setup, Work, Cleanup) :-
     call(Setup),
     call_cleanup(Work, Cleanup).

Do you use the Windowed version (swipl-win.exe) or the commandline version (swipl.exe)? Anything else loaded?

Anyone else who can confirm/deny handling of alarm/4 during sleep/1.

I use the Windowed version. I load the janus library if that’s what you mean.

It shouldn’t make a difference, but please test with just Prolog and nothing except for the alarm test loaded. On Windows you never know :frowning:

I found the cause of the interrupt problem ! :slight_smile:

@Jan
Since you proposed some code cleaning I’m calling start/0 with initialization/1. It seems that initialization/1 has an unexpected side effect. This is the reason why the following test script no longer does what it is intended to do : interrupt sleep/1 with alarm/4.

start :-
	alarm(2, writeln('Alarm'), _, [remove(true)]),
	count_down(5).

count_down(-1).
count_down(Secs) :-
	writeln(Secs),
	sleep(1),
	SecsMinus1 is Secs - 1,
	count_down(SecsMinus1).

:- initialization start.

Thanks. The solution is to use initialization/2:

:- initialization(start, main).

The initialization/1 directive is meant to initialize the module. The system disables interrupts while it runs to avoid the possibility that a module is only partially initialized. The main initialization/2 runs after all files in the application are loaded (and Prolog terminates after completion of the main goal unless the main goal calls cli_enable_development_system/0 at the end.