pythondockerflaskaudiopaas

Pygame MIDI Playback in PaaS from Dockerfile


The Problem:
I am currently deploying a Python Flask App using a Dockerfile on a PaaS (Heroku like). The User Story is as follows:

  1. User Clicks 'Generate MIDI'
  2. MIDI is generated and User can (on the same page):

I had some NotImplemented: no such file or dir... errors which have gone away since I added some install lines to my Dockerfile. Alas, I am stuck at this ALSA error:
pygame.error: ALSA: Couldn't open audio device: No such file or directory

I know for certain the file is a good directory as downloading proves the file exists and references the source. Locally, you can also see the file generate.

The Context:
Here is my Dockerfile:

# syntax=docker/dockerfile:1

FROM python:3.9-slim-buster

WORKDIR /appname

COPY requirements.txt requirements.txt
# Added to avoid NotImplemented errors
RUN apt-get update && apt-get install -yq \
    libgtk2.0-dev \  
    libasound2 \
    alsa-tools \
    # Original Run Command
    && pip3 install -r requirements.txt
# && rm -rf /var/lib/apt/lists/* \  # Doesn't work.

COPY . .

EXPOSE 5000

CMD [ "python3", "-m" , "flask", "--app", "appname", "run", "--host=0.0.0.0"]

Here is a pertinent snippet of my Flask App:

from pygame import mixer

        if request.form.get('playback_midi') and MIDI_file:
            if 'Playback MIDI' in request.form['playback_midi']:
                mixer.init()  # <-- Here is where it fails
                mixer.music.load("temp_MIDI_File.mid")
                mixer.music.play()

The "temp_MIDI_File.mid" is used so that it is constantly overwritten without unique file names (that is handled elsewhere).

What I've Tried:
Currently, I'm working with these references:
Run Apps Using Audio in a Docker Container
Getting pygame running in docker

I'm thinking the 'Run Apps Using Audio in a Docker Container' might have the solution in there, I'm just not sure if the same solution could possibly work in a PaaS context (as I'm not sure what audio device would possibly be usable in this cloud hosting scenario; or if I even have privileges to access it). I'm also open to using a different package besides Pygame. Thanks!

Further Research:
Running Sound in a Container
Docker Container Audio
Docker Tips - Play Audio in a Container (2022)
Maybe this is best handled in JS...

Leaving it for now...
I was able to Use the html <audio> tag to generate audio successfully. All of the controls work. This leads me to two complicated avenues I don't quite have time to pursue, but will put a pin in...

  1. Audio-Deviceless File Render - Using the FluidSynth API, perhaps I could convert the MIDI to a WAV and play the audio using the html audio tags. midi2audio seemed like a pythonic way to approach this, but seems configured on using ALSA just like pygame. The FluidSynth API is coded in C, so I'm not sure how to bridge this.
  2. Specify the device to be used by ALSA in the container - Perhaps I could specify --device in the Dockerfile to find the audio device and use a full Ubuntu image to launch it.

Solution

  • My Solution:

    Fortunately, there is an awesome html MIDI player that solved this issue:

    html-midi-player

    Here is my Flask HTML Template Code:

    {% if MIDI_playback %} 
        <midi-player
         src="{{  MIDI_playback }}"
         sound-font visualizer="#myPianoRollVisualizer">
        </midi-player>
        <midi-visualizer type="piano-roll" id="myPianoRollVisualizer"
         src="{{  MIDI_playback }}" style="color: white">
        </midi-visualizer>
        <script src="https://cdn.jsdelivr.net/combine/npm/tone@14.7.58,npm/@magenta/music@1.23.1/es`6/core.js,npm/focus-visible@5,npm/html-midi-player@1.5.0">
        </script>
    

    An Important Note:

    Using the script within this Python Flask App, I ran into this CORS error:

    No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled

    My workaround was to specify a CORS configuration in local and production instances.