MIDI output from Euterpea on Debian

Posted on 2024-12-23 by Ernesto Hernández-Novich
Tags: , , ,

I have a fork of Euterpea2 to explore algorithmic music manipulation and rendering. The library is able to render WAV or MIDI files, and that’s fine most of the time, as I will feed them to a DAW. However, there are times when I’d rather render a MIDI event sequence immediately to a MIDI controller or use a live MIDI sequencer.

Since I don’t always have access to a physical MIDI controller or sequencer, I tend to use a MIDI software synthesizer instead. Debian provides at least two options: timidity and fluidsynth. Both require a MIDI GM to do their thing and, thanks to standardization, one can easily use the Sound Blaster SoundFont.

Building Euterpea2 from source

Assuming you use Debian 11 or newer, in order to build Euterpea2 from source you’ll need:

  1. Install a few supporting libraries

    # apt install libportmidi-dev libasound2-dev
  2. Clone Euterpea2 as you would clone any other Github repo

    $ git clone https://github.com/Euterpea/Euterpea2.git
    Cloning into 'Euterpea2'...
    remote: Enumerating objects: 493, done.
    remote: Counting objects: 100% (104/104), done.
    remote: Compressing objects: 100% (18/18), done.
    remote: Total 493 (delta 90), reused 86 (delta 86), pack-reused 389 (from 1)
    Receiving objects: 100% (493/493), 19.05 MiB | 11.76 MiB/s, done.
    Resolving deltas: 100% (214/214), done.
  3. I prefer stack rather than cabal because of reasons. Upstream maintainers have never accepted my patches to support stack. If you like cabal better, figure it out. If you want to use stack, you’ll need

    $ cd Euterpea2
    $ cat >> stack.yaml
    resolver: lts-22.43
    
    packages:
    - .
    
    extra-deps:
      - PortMidi-0.2.0.0
    ^D
    $ stack build

Direct MIDI playback using timidity

I tend to use timidity for simple tests or when using a system that does not have a kernel compiled for real-time scheduling.

  1. Install timidity and the SoundFont GM

    # apt install timidity fluid-soundfont-gm

    Check that timitidy is configured to use the SoundFont GM.

    $ grep source /etc/timidity/timidity.cfg 
    #source /etc/timidity/freepats.cfg
    source /etc/timidity/fluidr3_gm.cfg
    #source /etc/timidity/fluidr3_gs.cfg
  2. Start timidiy on a terminal

    $ timidity -iA --sequencer-ports=1 -Os
    Requested buffer size 32768, fragment size 8192
    ALSA pcm 'default' set buffer size 32768, period size 8192 bytes
    TiMidity starting in ALSA server mode
    Opening sequencer port: 128:0

    it will sit there forever waiting. These particular options launch timidity as a single-port ALSA Sequencer (meaning it can receive MIDI events over the ALSA API) and output ALSA waveforms (meaning it will use the GM to synthesize sound via ALSA).

  3. On a different terminal, start an Euterpea2 REPL session and use Euterpea2’s devices functions to figure out if the single output sequencer is available

    $ cd Euterpea2/
    $ stack repl
    (...)
    ghci> devices
    
    Input devices: 
      InputDeviceID 1        Midi Through Port-0
    
    Output devices: 
      OutputDeviceID 0       Midi Through Port-0
      OutputDeviceID 2       TiMidity port 0
  4. Use it

    ghci> playDev 2 $ forever $ canon wn (trio rrryb)

    and you should hear music.

    What do you mean you don’t have functions canon, trio, and rrryb that make a cello, a viola, and a violin play a whole note delayed canon of «Row, row, row, your boat»?

    Once you’re done, just interrupt timidity.

Direct MIDI playback with fluidsynth

fluidsynth can be used for simple tests as well, sure. But, it uses less CPU than timidity and peforms better for complex MIDI compositions, when running on a system that does have a kernel compiled for real-time scheduling.

  1. Install fluidsynth and the SoundFont GM

    # apt install fluidsynth fluid-soundfont-gm
  2. Start fluidsynth on a terminal

    $ fluidsynth -si -a alsa /usr/share/sounds/sf2/FluidR3_GM.sf2
    FluidSynth runtime version 2.1.7
    Copyright (C) 2000-2021 Peter Hanappe and others.
    Distributed under the LGPL license.
    SoundFont(R) is a registered trademark of E-mu Systems, Inc.

    it will sit there forever waiting. These particular options launch fluidsynth as a non-interactive server, that will output to ALSA waveform (meaning it will use the GM to synthesize sound via ALSA).

  3. On a different terminal, start an Euterpea2 REPL session and use Euterpea2’s devices function to figure out if the single output sequencer is available

    $ cd Euterpea2/
    $ stack repl
    (...)
    ghci> devices
    
    Input devices: 
      InputDeviceID 1        Midi Through Port-0
    
    Output devices: 
      OutputDeviceID 0       Midi Through Port-0
      OutputDeviceID 2       Synth input port (94506:0)
  4. Use it

    ghci> playDev 2 $ forever $ canon wn (trio rrryb)

    and you should hear music live.

    Once you’re done, just interrupt fluidsynth.

You can have fluidsynth running permanently as a daemon accessible to your user only. This is useful if your system is never going to be connected to an actual MIDI sequencer, you have CPU cores to spare, and plan on doing live MIDI-to-sound sequencing (from Euterpea2 or a DAW). In that case:

  1. Modify /etc/default/fluidsynth to set the service’s configuration

    $ cat /etc/default/fluidsynth
    # Mandatory parameters (uncomment and edit)
    SOUND_FONT=/usr/share/sounds/sf2/FluidR3_GM.sf2
    
    # Additional optional parameters (may be useful, see 'man fluidsynth' for further info)
    OTHER_OPTS='-a alsa -m alsa_seq -r 48000'
  2. Enable and start the fluidsynth service for your user

    $ systemctl --user enable fluidsynth.service
    Created symlink /home/emhn/.config/systemd/user/multi-user.target.wants/fluidsynth.service → /usr/lib/systemd/user/fluidsynth.service.
    $ systemctl --user start fluidsynth.service
    $ systemctl --user status fluidsynth.service
     fluidsynth.service - FluidSynth Daemon
         Loaded: loaded (/usr/lib/systemd/user/fluidsynth.service; enabled; vendor preset: enab>
         Active: active (running) since Mon 2024-12-23 16:08:19 PST; 54s ago
           Docs: man:fluidsynth(1)
       Main PID: 97095 (fluidsynth)
          Tasks: 6 (limit: 18553)
         Memory: 186.1M
            CPU: 8.477s
         CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/fluidsynth.service
                 └─97095 /usr/bin/fluidsynth -is -a alsa -m alsa_seq -r 48000 /usr/share/sounds>
    
    Dec 23 16:08:18 trillian systemd[2994]: Starting FluidSynth Daemon...
    Dec 23 16:08:19 trillian systemd[2994]: Started FluidSynth Daemon.

    and the rest works all the same.

Bonus track

I open PerfectPiano on my Android Phone, then plug to the system using an USB-C cable. Swipe down and tap «USB Preferences», select «MIDI». And then…

ghci> devices

Input devices: 
  InputDeviceID 1        Midi Through Port-0
  InputDeviceID 3        Pixel 8 Pro MIDI 1

Output devices: 
  OutputDeviceID 0       Midi Through Port-0
  OutputDeviceID 2       Pixel 8 Pro MIDI 1

It should be as easy on vendor locked-in devices, I reckon.