Windows MIDI interface

Thank you very much. I have Java installed on my PC and I added the directory holding jvm.dll to %PATH% as was advised in the first try.

Then I tried your demo code fragment for Java again, but I get:

?- jpl_midi_demo.

ERROR: c:/program files/swipl/library/jpl.pl:5429:
ERROR:    open_shared_object/3: The specified module could not be found.

ERROR: c:/program files/swipl/library/jpl.pl:5429:
ERROR:    c:/program files/swipl/library/jpl.pl:5429: Initialization goal raised exception:
ERROR:    jpl:extend_java_library_path/1: Unknown procedure: jpl:jni_get_default_jvm_opts/1
ERROR: Exported procedure jpl:jpl_c_lib_version/1 is not defined
ERROR: Unknown procedure: jpl:jni_func/3
ERROR: In:
ERROR:   [15] jpl:jni_func(6,'javax/sound/midi/MidiSystem',_43364)
ERROR:   [14] jpl:jFindClass('javax/sound/midi/MidiSystem',_43396) at c:/program files/swipl/library/jpl.pl:1512
ERROR:   [13] jpl:jpl_type_to_class(class([javax|...],['MidiSystem']),_43428) at c:/program files/swipl/library/jpl.pl:2979
ERROR:   [12] jpl:jpl_call('javax.sound.midi.MidiSystem',getSynthesizer,[],_43482) at c:/program files/swipl/library/jpl.pl:314
ERROR:   [11] jpl_midi_demo(20) at c:/docs/jpl_midi_demo.pl:7
ERROR:    [9] toplevel_call('<garbage_collected>') at c:/program files/swipl/boot/toplevel.pl:1173
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.
   Exception: (15) jpl:jni_func(6, 'javax/sound/midi/MidiSystem', _32922) ?

I’m running SWI-Prolog (threaded, 64 bits, version 9.1.14-41-g41ac4a569).

One of the familiar nightmares :frowning: Just tried in in a Windows 11 VM using Oracle Java downloaded from java.com. Works fine.

With Oracle Java, changing %PATH% should not be required. It finds Java through the Windows registry and adds it to the search path. It should print an informational (green) message that it added the bin and server directories of your Java installation to the DLL directories.

Note that you must have installed the 64 bit versions of both Prolog and Java (or the 32 bit versions, but that is old school) . Mixing 32 and 64 bit versions does not work.

To test, simply do this after starting Prolog:

?- [library(jpl)].

I re-installed Java with the newest release and now jpl_midi_demo/0 works fine. This makes Java the clear winner, but I’ll still try to install mido (with MIDI ports support) with the help of the Python community.

I’ll now start studying the Java MIIDI interface for receiving MIDI messages as well.

Thanks for all the help and advice.

1 Like

Mido and a port interface have been installed. Mido (port) calls in Python work properly.

>>> import mido
>>> msg = mido.Message('note_on', note=60)
>>> mido.get_output_names()
['CoolSoft MIDIMapper 0', 'Microsoft GS Wavetable Synth 1', 'loopMIDI Port 2', 'loopMIDI Port 1 3', 'loopMIDI Port 2 4']
>>> outport = mido.open_output('loopMIDI Port 2')
>>> mido.get_input_names()
['loopMIDI Port 0', 'loopMIDI Port 1 1', 'loopMIDI Port 2 2']
>>> inport = mido.open_input('loopMIDI Port 2 2')
>>> msg = inport.receive()
>>> msg.bytes()
[144, 16, 115]
>>> outport.send(msg)
>>>

The interface between SWI Prolog and Python works fine :

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

true.

The interface between Prolog and mido doesn’t work yet. What is exactly needed to call mido methods in Python? I’ve been reading section('packages/janus.html') but this didn’t help.

?- py_load_module('mido', [send/1, open_output/2]).
ERROR: Unknown procedure: py_load_module/2 (DWIM could not correct goal)

?- py_call(mido:'Message'('note_on', note=60), Msg).
ERROR: Python 'ModuleNotFoundError':
ERROR:   No module named '_socket'
ERROR: In:
ERROR:   [10] janus:py_call(mido:'Message'(note_on,...),_3624)
ERROR:    [9] toplevel_call(user:user: ...) at c:/program files/swipl/boot/toplevel.pl:1173

?- py_call(mido:send(Port, 'note_on', note=Note, velocity=Velocity)).
ERROR: Python 'ModuleNotFoundError':
ERROR:   No module named '_socket'
ERROR: In:
ERROR:   [10] janus:py_call(mido:send(_172,note_on,...,...))
ERROR:    [9] toplevel_call('<garbage_collected>') at c:/program files/swipl/boot/toplevel.pl:1173

You don’t need to import modules. They are implcitly imported by calling py_call(Module:Something, Result). But …

This is weird. That should just work (and does for me). A search hints at a solution: the embedded Python may be a different environment from the Python you use interactively. The advice is to set PYTHONHOME. See python - ImportError: No module named _socket - Stack Overflow

Note that you can get an interactive Python shell using ?- py_shell. from Prolog. Then you have the same environment.

Note that Port, Node and Velocity may not be variables. I guess Port should come from an mido.open_output call. Also mido.send() doesn’t exist and send is a method on the output. This works for me (but produces no sound, I guess I’m still missing some option, call to open something or maybe even midi support in my audio). The port name is for my Linux system. I know practically nothing about midi or mido …

test :-
    py_call(mido:open_output('Midi Through:Midi Through Port-0 14:0'), Port),
    py_call(mido:'Message'('note_on', note=60), Msg),
    py_call(Port:send(Msg)).

No clue why there is no sound on my Linux device. Trying on windows, I do get sound after installing these Python packages. Note that there is a package called rtmidi, but that is not the right one.

pip install mido
pip install python-rtmidi

Using this Prolog program, which plays on the first midi channel it can find. Note that this requires the daily build for Windows rather than 9.1.15 to get the Janus eval() support. On older versions you need to create the message and pass it to Port:send() in two calls.

play :-
    Note = 60,
    Velocity = 64,
    py_call(mido:get_output_names(), [OutputName|_]),
    py_call(mido:open_output(OutputName), Port),
    py_call(Port:send(eval(mido:'Message'(note_on, note=Note, velocity=Velocity)))),
    sleep(1),
    py_call(Port:send(eval(mido:'Message'(note_off, note=Note, velocity=0)))),
    py_call(Port:close()).

After creating the PYTHONHOME environment variable and restarting Windows, your test example works fine with my MIDI out ports.

Again, thanks for your assistance.

I downloaded rtmidi with the command “pip download mido[ports-rtmidi]”. See discussion on Offline installation · mido · Discussion #544 · GitHub

I’m going to try your ‘play’ example tonight. I’m not sure about the channel(s) used.

Concerning your MIDI out port on Linux; it looks as if you’re using a port that is just forwarding messages. In order to hear sound, you need a device (hardware or software) with a MIDI in port that receives the messages you send (with an out port) and is connected to an audio or sound system. Note that if you can play MIDI files you should also be able to hear individual messages as the infrastructure is there.
The easiest way for me to test whether messages are send properly in Windows is to use tools like MIDI Tools or MIDI-OX.
Unfortunately I couldn’t find something similar for Linux. Most tools on Linux are command driven.
These links might help :

Thanks for the pointers. In the meanwhile I do have MIDI sound on Linux. It seems you are right about a missing device. I though, as Firefox could play midi, this would all be present. I think FF uses some software renderer though.

The solution (if you do not have proper midi hardware) is to install the Debian package timidity-daemon. Now this suffers from some installation and permission issues in Ubuntu 22.04. I could start timidity by hand using

 timidity -iA

After which my play example works also on Linux. Be it that it sounds ugly, but I guess that has to do with properly configuring the MIDI channels. This works fine:

mido3-play -o 'TiMidity:TiMidity port 0 128:0' ~/MIDI_sample.mid

Your Play example works fine with the latest build. It selects the first out port and plays a note on the first (default) channel, which is numbered 0.

Here is another example :

/*
Predicate py_demonstrate_soundbank/0 plays all patches on channel 6 of the first MIDI out port it finds. 
Note that if several synthesisers are listening to this port and channel, several notes (and coresponding patches) will be played simultaneously. 
The number of patches depends on the number of patches contained in the selected soundbank (Yamaha DX7 or Dexed VST has only 32, but more recent synths and General MIDI impose a minimum of 128 patches). Note that patches are numbered starting from 0 internally.
*/

py_demonstrate_soundbank :-	% py mido port management
	py_call(mido:get_output_names(), [OutputName|_]),	% take the first port found
	py_call(mido:open_output(OutputName), OutPort),
	NumberOfPatches = 64,	% set your soundbank's number of patches
	py_midi_demonstrate_patch(OutPort, 0, NumberOfPatches),
	py_call(OutPort:close()).

py_midi_demonstrate_patch(OutPort, PatchNumber, NumberOfPatches) :-	% iterates through all patches
	(	PatchNumber @< NumberOfPatches
	->	Pitch = 60,		% pitch is C in the 6th octave
		Volume = 70,	% volume/loudness is mezzoforte
		Channel = 5,	% set the channel of your sound module; 5 internally, but 6 (=5+1) externally
		Duration = 1,	% duration is approximately 1 second
		py_midi_play_note(OutPort, Channel, Pitch, Volume, PatchNumber, Duration),
		NextPatchNumber is PatchNumber+1,
		py_midi_demonstrate_patch(OutPort, NextPatchNumber, NumberOfPatches) 	% play remaining notes
	;	true
	).
	
py_midi_play_note(OutPort, Channel, Pitch, Volume, PatchNumber, Duration) :- % py mido note playing
	py_call(OutPort:send(eval(mido:'Message'(program_change, channel=Channel, program=PatchNumber)))),
	py_call(OutPort:send(eval(mido:'Message'(note_on, channel=Channel, note=Pitch, velocity=Volume)))),
	sleep(Duration),	% play note for Duration seconds
	py_call(OutPort:send(eval(mido:'Message'(note_off, channel=Channel, note=Pitch, velocity=0)))).

Note that music usually consists of simultaneously played melodies (polyphony) or notes (chords). Sleep/1 can’t deal with this as it blocks all further calls. That’s why I started a discussion on time and duration parameters · mido · Discussion #550 · GitHub . I’ve programmed these in the past, using timers and event queues, but re-implementing them on every platform I use is boring.

Thanks. Sounds as I think it is intended :slight_smile: Guess the composition need a little more work?

Yes. That is an interesting challenge. Prolog has a couple of timing primitives, some contributed by @samer, notably for absolute timings. I know he is into music too, so that could not be a coincident :slight_smile: The most simple to use is probably alarm_at/3 and friends. I don’t know whether that is sufficiently accurate and scales well enough with lots of pending timers. You probably cannot set them in advance for all notes to play. You can have one or more threads that schedule them a little in advance. The resolution depends on the OS. I think it should be 1ms for Windows and quite a bit shorter on POSIX systems.

Another promising building block is threads that can possibly deal each with an instrument. thread_get_message/3 allows for accurate timeouts. With some fantasy you have have agents as instruments and a conductor :slight_smile:

The way to deal with this in Prolog is to first think about how you would like to represent the music, finding a representation that is easy to handle for what you want to do. Next, you try to find suitable connection to MIDI using either Prolog’s timing or something external.

This musical genre is called minimalism. :wink: It usually tests my nerves and endurance, but here it is intended to listen quickly to what you have in your soundbanks.

I expect the mido facilities to be insufficient in the short term and as my experience with Python is limited, I need to think about which platform will play the role of ‘conductor’.

The timing facilities of Prolog are similar to what I see in other languages but I don’t like re-inventing the wheel. I particularly like the idea of blackboards explained in SWI-Prolog -- Waiting for events .

I will continue using the facilities of my other platforms that are already in place to schedule events, until I can find the time to create something better.

I use queues when dealing with longer sequences. Timers are good for scheduling (near) real time events. Even with these precautions it’s still very easy to overwhelm any MIDI system with for instance continuous controller messages. Also, some contemporary synths have sounds that are defined by more than a thousand parameters. Controlling these sounds algorithmically in (near) real time is a challenge but also the beauty of music.

ATM receiving MIDI messages in Prolog is of higher importance to me than the scheduling and time management issue.

Good catch. This may indeed be the best building block.

Calling through Python is (now) nice and easy but, as Python talks to a C++ library we can also bypass Python. I did something similar for the ROS robotics interface. The Prolog interface is considerably faster than the Python one, you no longer need the Prolog ↔ Python bridge and you get proper multi-core support. @peter.ludemann likes C++. If he also likes music …

That is probably a lot easier. I guess the MIDI message already carries a timestamp?

I added some support for absolute (rather than relative) time alarms. I do think that when programming music, it’s good to get a firm grip on time, and being clear about when things are supposed to happen in absolute time is helpful, rather than relying on relative time delays, where errors can accumulate. Also you can then have a definite strategy for what to do if an event cannot be played at its scheduled time, eg drop it rather than play it at the wrong time.

In Prolog, I relied on absolute time callbacks to wake up the code slightly before each event was due, and then scheduled the event, with its timestamp, with the MIDI system (or OSC for sending messages to Supercollider or whatever) so that the lower level system was responsible for playing the event accurately at the right time. Another alternative, which you could do in the Python part of the code, is, given a target absolute time, go into a tight loop polling a high resolution timer and drop out of it at the right time and immediately send your MIDI message. If you use a Prolog timer to wait to just before the scheduled event time, you won’t spend too long in the loop.

There was an idea going around the music programming community some years ago called ‘strongly-timed programming’ (in analogy to strongly typed programming), where the idea was that time should be embedded in the semantics of the language rather than left as a sort of by-product of execution. There was a language called ‘Chuck’ built around that idea.

There was also a music language/system called Impromptu, which relied heavily on the idea of ‘timed continuations’, that is, you would implement a timed process as a chunk of code that did what needed doing now and would then register a chunk of code to be run at a given time in the future. Impromptu was a Scheme-like language, but you can do something similar in Prolog by having a predicate that returns a time and a callable term (the continuation) to be called at that time. These timed continuations are somewhat composable - eg if you have C1 due at time T1 and C2 due at time T2, you can build the concurrent process (C1 & C2) due at min(T1,T2), which schedules its remainer at max(T1,T2).

For one project, I extend this idea to handle input events, by having each process defined as two continuations: one for dealing with an input event and the other to run at a specified time if nothing else happened. (They were basically cooperative, lightweight threads.) It worked quite well and was able to handle several concurrent musical processed being driven by events coming from different people, so I would strongly recommend Prolog for doing this - Prolog gives you the power to build whatever abstraction you like to express your system naturally. The performance was fine too (even on the hardware of 15 years ago).

BTW, if you’re prepared to try something other than MIDI, you could try controlling your synth using OSC messages - plosc will let you do that and is not Mac specific as it uses libosc underneath. It works well with Supercollider, but you could also control a DAW like Ableton using OSC messages. plosc also lets you receive OSC messages.

3 posts were split to a new topic: Janus and Python venv

MIDI messages don’t carry timestamps, as that would make them twice as long. MIDI was created in the Middle Ages of electronic music and resources were much more limited. A MIDI file contains timestamped (or delta) records of events. Time is measured at two levels : in ticks/pulses per quarter note (resolution) and in bpm (tempo).

The following test script in Python uses a callback function to handle incoming MIDI messages:

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

import mido

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

import time 

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

C:.…\Py>test.py

.
note_on channel=0 note=23 velocity=97 time=0
note_off channel=0 note=23 velocity=97 time=0
note_on channel=0 note=17 velocity=105 time=0
note_off channel=0 note=17 velocity=105 time=0
control_change channel=0 control=123 value=0 time=0
control_change channel=0 control=121 value=0 time=0

I have no idea of how I would translate this into Prolog. In other languages I usually use callback routines to handle and filter incoming MIDI messages and my scripts keep waiting for these MIDI events without wasting CPU time.

You call Prolog from your callback, as in

import janus

def call_prolog(message):
    janus.once("writeln(Msg)", {'Msg':message})

That will print the message as object handle. You can use py_call/2 on the message to get the properties you are interested in. Probably it is easier to create a Python dict with all the info you want from the message and pass that to Prolog, so you get a Prolog dict.

From there it depends what you want to do with the message … You can assert them, you could also send them to a message queue and have some Prolog thread use library(lazy_list) to turn the message stream into a lazy list and run a DCG on them :slight_smile:

Unfortunately, janus for python is not installed here. How can it be downloaded and installed on a gapped Windows PC? I suppose installing janus · PyPI won’t help.

See GitHub - SWI-Prolog/packages-swipy: Python interface for SWI-Prolog. It requires pip and visual studio 22 (and Python and SWI-Prolog).

This should become simpler. Exactly how is still a bit of an open issue. Suggestions are surely welcome. pip from source works fine. I had a look at (ana)conda. It is probably not hard to create a conda-forge package that provides SWI-Prolog and Janus. I have a tentative recipe for Conda at GitHub - SWI-Prolog/swi-prolog-feedstock. Compiles on Linux and MacOS and for a large part on Windows. I wonder how welcome that is.

I like C++ better than C, that’s all. (And also better than JavaScript, PHP, Perl, …).
I also like music, I was working on my Grade 9 piano Royal Conservatory when I finished high school (equivalent to Grade 7 of the Royal School of Music, I think), but I haven’t kept it up. :frowning:
My electric piano supports MIDI, but it’s rather old, so I’d need some kind of MIDI-to-USB converter I suppose.

Anyway, if you want help in writing a foreign function in C++, you know where to find me.

1 Like

Since I want to install on an off-line PC I suppose I need to download the janus_swi package on an on-line PC. This gives the following error :

E:\USB-S1\Apps\Python>python -m pip download janus_swi
ERROR: Could not find a version that satisfies the requirement janus_swi (from versions: none)
ERROR: No matching distribution found for janus_swi

Note that for security reasons I can’t install any software on the on-line PC. I can’t reconfigure the on-line PC for the same reasons. Therefore I’m not sure the setup.py script can be run on the on-line PC. I can only download on my USB stick. The stick also contains a Python installation.