[ANN] PortMidi.jl

PortMidi.jl

This is mostly a fun project I did in the last few days to play music with Julia and to learn how to use BinaryBuilder.jl (thanks @giordano for all the corrections of my beginner mistakes).

PortMidi.jl is a mostly autogenerated wrapper of PortMidi/portmidi: portmidi is a cross-platform MIDI input/output library (github.com), allowing us to play music instruments (and other devices) via MIDI on Linux, macOS and Windows.

The underlying C library itself is very portable but hence also minimal. It can:

  • Open and close existing MIDI devices
  • (Create new virtual MIDI devices on some platforms)
  • Send and receive MIDI messages in real-time
  • A few things regarding synchronization and timing for real-time applications

I could imagine that it could be useful in JuliaMusic @Datseris? Or maybe it could help to play music with MusicTheory.jl @dpsanders?

EDIT: That was fast, it is now part of JuliaMusic :partying_face:

Example for generating music

On a computer with some MIDI instrument listening, one can essentially do something like:

using PortMidi, MusicTheory
using MusicTheory.PitchNames

id = 1  # change according to setup
Pm_Initialize()
stream = Ref{Ptr{PortMidi.PortMidiStream}}(C_NULL)
Pm_OpenOutput(stream, id, C_NULL, 0, C_NULL, C_NULL, 0)

# 3. Send MIDI messages to play notes  (0x90: Note ON, 0x80: Note OFF)
function play_note(stream, note, velocity, duration)
    Pm_WriteShort(stream[], 0, Pm_Message(0x90, semitone(note), velocity)) 
    sleep(duration)
    Pm_WriteShort(stream[], 0, Pm_Message(0x80, semitone(note), velocity))
end

# 4. play a melody
@sync begin
    for (base, type) in zip([C[4], G[4], A[4], F[4]], [Major, Major, Minor, Major])
        for rep in 1:3
            @async play_note(stream, base, 100, 0.3)
            @async play_note(stream, base + Interval(3, type), 100, 0.25, 0.05)
            @async play_note(stream, base + Interval(5, Major), 100, 0.28, 0.02)
            sleep(0.4)
        end
        sleep(0.4)
    end
end

# 4. Close device
Pm_Close(stream[])

Here is a small and not very musical melody ‘Random Melody’ generated with this script example script.

Setup for actually hearing sound

On Windows, one might want to use loopMIDI to create a virtual MIDI device and then some VSTs in a DAW or VCV Rack to generate some sound from the incoming MIDI. (It’s off-topic, but feel free to PM me if you need advice for the setup.)

Next?

I didn’t add much to the generated wrapper yet, since I am only interested in playing music and that is simple enough.

  • The main question I need to answer is: Will people use it, and if, should extra functionallity go into a separate package or directly to PortMidi.jl?

Any suggestions are very welcome.


PS: My dream, in another package, is to do something like Extempore docs (extemporelang.github.io) but with pure Julia. No idea yet how to manage the hot-loading of new coding while playing music :wink:

10 Likes

Awesome!!!

In JuliaMusic I have been generating music by making MIDI sequences, saving them, and then loading them in either MuseScore or Cubase to play music.

This package could probably streamline this allowing me to play directly on a device, although I don’t have any music instruments with me yet to test anything!

MIDI.jl has formal support for reading MIDI data, and writing MIDI data. It shouldn’t be too hard to make your package have the MIDI.Track as an input (or MIDI.Note). They use the same UInt8 format that raw MIDI uses.

Would be great if you could write a tutorial about this in the package docs. I will bother you otherwise when I am ready to go back to music!

1 Like

Very nice and thanks for welcoming me to JuliaMusic :wink:

I’ll see to add some tutorial on how to set it up to the package docs, I guess with most people from the scientific background that might help also others to try it out.

I’ll try it, but there is some technical depth to it, as for longer pieces with many messages one needs to use some buffers and share a timer to make it work properly. But it will also be a good test case either way.

@Datseris I added some description about how to set it up:
Setup · PortMidi.jl (juliamusic.github.io)

(It’s not tested for Linux/macOS. I’ll test Linux at some point.)

1 Like