Running IO Operations from Python with Janus

I am trying to create a midi file using the python midiutil library. Here is my code:

:- module(midi, [main/1]).
:- use_module(library(janus), [py_call/1, py_call/2]).

% you need midiutil installed
% pip install midiutil

:- table create_midi/1.

create_midi(MIDI) :-
    py_call(midiutil:'MIDIFile'(1), MIDI).

add_note(MIDI, Track, Chan, Pitch, Time, Dur, Vol) :-
   py_call(MIDI:addNote(Track, Chan, Pitch, Time, Dur, Vol)).

prog_change(MIDI, Track, Chan, Time, Instr) :-
    py_call(MIDI:addProgramChange(Track, Chan, Time, Instr)).

add_tempo(MIDI, Track, Time, Tempo) :-
    py_call(MIDI:addTempo(Track, Time, Tempo)).

write_file(MIDI, FileName) :-
    py_call(open(FileName, "wb"), File),
    py_call(MIDI:writeFile(File)),
    py_call(File:close()).

main([]) :-
    create_midi(MIDI),
    prog_change(MIDI, 0, 0, 0, 1),
    add_tempo(MIDI, 0, 0, 120),
    add_note(MIDI, 0, 0, 60, 0, 1, 100),
    add_note(MIDI, 0, 0, 61, 1, 1, 100),
    write_file(MIDI, "test.mid").

What it does it adds two notes to the MIDI object, and writes them to the file test.mid. The problem is, I am not sure how to run this and create the file.

If I consult the file and do ?- main([]). I get this error:

ERROR: Python 'UnsupportedOperation':
ERROR:   read
ERROR: In:
ERROR:   [14] janus:py_call(<py_MIDIFile>(000002419c796150):writeFile([]))
ERROR:   [13] midi:write_file(<py_MIDIFile>(000002419c796150),"C:/Users/vatis/OneDrive/Documents/Sikum/backend/test.mid") at c:/users/vatis/onedrive/documents/sikum/backend/midi.pl:23
ERROR:   [11] toplevel_call(user:user: ...) at c:/program files/swipl/boot/toplevel.pl:1317
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: (4) setup_call_cleanup('$toplevel':notrace(call_repl_loop_hook(begin, 0)), '$toplevel':'$query_loop'(0), '$toplevel':notrace(call_repl_loop_hook(end, 0))) ? creep

So I tried to compile the file to and executable and run it. I did that with the command swipl -o midi.exe -c midi.pl --goal=main, but when running the executable I get this error:

ERROR: source_sink `library('python/janus.py')' does not exist
ERROR: In:
ERROR:   [33] throw(error(existence_error(source_sink,...),_298))
ERROR:   [28] janus:py_initialize('c:/Users/vatis/OneDrive/Documents/Sikum/backend/midi.exe',[],[]) at c:/users/vatis/onedrive/documents/sikum/library/ext/swipy/janus.pl:921
ERROR:   [26] <meta call>
ERROR:   [25] janus:py_call(midiutil:'MIDIFile'(1),_394) <foreign>
ERROR:   [23] call(midi:<closure>(midi:create_midi/1)(_438)) at c:/users/vatis/onedrive/documents/sikum/boot/init.pl:501
ERROR:   [22] reset(midi:call(...),_464,_466) at c:/users/vatis/onedrive/documents/sikum/boot/init.pl:603
ERROR:   [21] delim(ret(_510),midi:call(...),2437700289216,[]) at c:/users/vatis/onedrive/documents/sikum/boot/tabling.pl:606
ERROR:   [20] activate(ret(_552),midi:call(...),2437700289216) at c:/users/vatis/onedrive/documents/sikum/boot/tabling.pl:584
ERROR:   [19] run_leader(ret(_598),midi:call(...),fresh(2437700404384,2437700289216),_592,_594) at c:/users/vatis/onedrive/documents/sikum/boot/tabling.pl:570
ERROR:   [18] setup_call_catcher_cleanup('$tabling':'$idg_set_current'(_654,<trie>(00000237912be890)),'$tabling':run_leader(...,...,...,_672,_674),_642,'$tabling':finished_leader(_684,_686,...,...)) at c:/users/vatis/onedrive/documents/sikum/boot/init.pl:676
ERROR:   [17] create_table(<trie>(00000237912be890),fresh(2437700404384,2437700289216),ret(_732),midi:create_midi(_742),midi:call(...)) at c:/users/vatis/onedrive/documents/sikum/boot/tabling.pl:384
ERROR:   [16] catch('$tabling':create_table(<trie>(00000237912be890),...,...,...,...),deadlock,'$tabling':restart_tabling(<closure>(midi:create_midi/1),...,...)) at c:/users/vatis/onedrive/documents/sikum/boot/init.pl:564
ERROR:   [15] start_tabling_2(<closure>(midi:create_midi/1),midi:create_midi(_856),midi:call(...),<trie>(00000237912be890),fresh(2437700404384,2437700289216),ret(_876)) at c:/users/vatis/onedrive/documents/sikum/boot/tabling.pl:370
ERROR:   [12] midi:main([]) at c:/users/vatis/onedrive/documents/sikum/backend/midi.pl:27
ERROR:   [11] catch(user:main([]),_932,user:throw(_954)) at c:/users/vatis/onedrive/documents/sikum/boot/init.pl:564
ERROR:   [10] catch_with_backtrace(user:main([]),_980,user:throw(_1002)) at c:/users/vatis/onedrive/documents/sikum/boot/init.pl:644
ERROR:    [9] prolog_main:main at c:/users/vatis/onedrive/documents/sikum/library/main.pl:121
ERROR:    [8] catch(user:main,_1054,'$toplevel':true) at c:/users/vatis/onedrive/documents/sikum/boot/init.pl:564
ERROR:    [7] catch_with_backtrace(user:main,_1094,'$toplevel':true) at c:/users/vatis/onedrive/documents/sikum/boot/init.pl:644
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.

Which doesn’t make too much sense. Why is it accessing library('python/janus.py').

I wonder if someone can help me out with this? Thanks in advance

The incorrect is because a BufferedWriter is apparently a list. This can be fixed by using

write_file(MIDI, FileName) :-
    py_call(open(FileName, "wb"), File, [py_object(true)]),
    py_call(MIDI:writeFile(File)),
    py_call(File:close()).

Then the code runs fine. In general, I’d consider adding a Python function for performing larger sequences of Python calls, i.e., define write_file/2 as a Python function.

I can’ t reproduce that. From the paths I assume the platform is Windows. Which Prolog and Python versions? Is this the SWI-Prolog binary downloaded from swi-prolog.org?

Hello! Thank you so much for the fix, I will try it out.

As for the executable error, this is on windows. Im using swi-prolog 9.2.4, and python 3.12.2. And yes, I downloaded the binary from swi-prolog.org.

Thanks. I need to think how to deal with Python files when creating an executable. As a work-around, you can set the environment variable SWI_HOME_DIR to the installation directory, e.g., by default C:\Program Files\swipl. Then the exe picks up janus.py from the installed system.

I will try that! Thank you for your help