Windows MIDI interface

The SWI-Prolog-Janus-Python-Mido interface and timers are working fine, but now I’m testing Mido’s MIDI file creation. I’m trying to translate the following Python code fragment to SWI-Prolog with Janus calls and I need some help. The following Python script works and creates a file with one note.

from mido import Message, MidiFile, MidiTrack

mid = MidiFile(type=1)
track = MidiTrack()
mid.tracks.append(track)
track.append(Message('program_change', program=12, time=0))
track.append(Message('note_on', note=64, velocity=64, time=32))
track.append(Message('note_off', note=64, velocity=127, time=32))
mid.save('new_song.mid')

Here is what I have in Prolog using janus :

:- use_module(library(janus)).

start :-
        py_call(mido:'MidiFile'(type=1), Outfile),
	writeln(outfile(Outfile)),
	py_call(mido:'MidiTrack'(), Track),
	writeln(track(Track)),
	py_call(Outfile:tracks:append(eval(mido:'MidiTrack'(Track)))),
	py_call(Track:append(eval(mido:'Message'(program_change, program=12, time=0)))),
	py_call(Track:append(eval(mido:'Message'(note_on, note=64, velocity=127, time=32)))),
	py_call(Track:append(eval(mido:'Message'(note_off, note=64, velocity=127, time=32)))),
	py_call(Outfile:save(eval(mido:'MidiFile'(file='C:/docs/new_song.mid')))).

The fourth py_call results in an error. The return value Track seems to be an empty list instead of an object.

?- start.
outfile(<py_MidiFile>(000001f317e7bad0))
track([])
ERROR: Type error: `py_callable' expected, found `[]' (an empty_list)
ERROR: In:
ERROR:   [11] janus:py_call([]:append(...))
ERROR:   [10] start at c:/....pl:128
ERROR:    [9] toplevel_call('<garbage_collected>') at c:/program files/swipl/boot/toplevel.pl:1173

The last call results in another error. My syntax might be wrong.

?- start.
outfile(<py_MidiFile>(0000025ee796bad0))
ERROR: Python 'AttributeError':
ERROR:   'str' object has no attribute 'read'
ERROR: In:
ERROR:   [11] janus:py_call(<py_MidiFile>(0000025ee796bad0):save(...))
ERROR:    [9] toplevel_call('<garbage_collected>') at c:/program files/swipl/boot/toplevel.pl:1173

You want the Python object rather than its Prolog representation. You can do that using

py_call(mido:'MidiTrack'(), Track, [py_object(true)])

That said, I’m not sure that is how one should use Janus. I’d be tempted to define a little abstraction in Python itself such that you need less and possibly simpler calls between Prolog and Python. That implies creating a Python file or, in recent versions, using py_module/2, which allows creating a Python module from Prolog such that you can keep everything together in a single file.

I’m not sure about the right balance here. In the original XSB Janus version you almost always had to write Python. The current version allows for doing much more from Prolog. The right balance is a matter of taste and experience.

Considering my experience using Prolog from C, I’d say create a Prolog program that is easily accessible from C rather than trying to do a lot of Prolog things from C. Something similar probably applies here too.

Thanks for the explanation. I note that in the first call I get a file object without using the py_object(true) option.

The last call

gives an error nevertheless. Is this a syntactic issue? Based on mido’s documentation I understand that the save method has a file ‘key’.
I also read that the file is not necessarily closed after using the save method, which might also cause some problems.

Python objects that have no natural Prolog counterpart are always returned as object reference. See SWI-Prolog -- Data conversion

I don’t know. Looks like invalid use of the Python API. It is surely something different from what you do in the original Python code, where you simply pass a string to save(). It looks a bit weird to pass a file to a file object for saving. But, I’m not familiar with this Python interface and I’ve seen too few Python interfaces to have developed a feeling on what they normally look like.