Guide

This is a short guide to using tinysoundfont. If you want details on specific API functions look in the Reference.

What is it?

The tinysoundfont Python package lets you generate audio using SoundFont instruments (.sf2, .sf3, or .sfo files) in Python. The audio data can be played by PyAudio in a separate thread if requested. This package also includes support for loading and playing MIDI data using a SoundFont instrument.

What you might want to use this package for:

  • Play MIDI files with SoundFonts in Python

  • Play MIDI files with modifications and customizations (mute tracks, change instruments, etc.)

  • Procedurally generate and play music or sounds

  • Play music and sounds in an interactive way in your Python program

Goals

The goals of tinysoundfont are:

  1. To be easy to install with as few native dependencies as possible

  2. To allow for note generation and musical experimentation quickly with minimal code

  3. To provide a consistent API

  4. To have permissive licensing (MIT)

Installation

This package depends on pyaudio for playing sounds. To install pyaudio for common platforms:

py -m pip install pyaudio

To install tinysoundfont, for all platforms do:

pip install tinysoundfont

If you have a less common platform then binary wheels may not be available. In this case pip will attempt to build the package locally from the source distribution so you will need to have a working local development environment installed.

Getting SoundFonts

Example Piano

In order to use this package to do anything interesting you need at least one SoundFont. For testing purposes this package contains a small SoundFont that can be downloaded:

florestan-piano.sf2

This example SoundFont contains a single instrument, an artificial piano.

Example Compressed SoundFont

To demonstrate compressed SoundFonts, this package contains a small collection of 17 instruments in compressed format that can be downloaded at:

florestan-subset.sfo

Even though this SoundFont contains the piano instrument and 16 other instruments, the total file size is smaller than florestan-piano.sf2 in uncompressed format.

GM SoundFont FluidR3_GM

Many SoundFonts aim to cover all 128 of the General MIDI instruments. This allows playback of any MIDI file that uses the same General MIDI standard. Other SoundFonts are designed to provide instruments that represent specific physical instruments, vintage synthesizers, retro video game console systems, or other types of stylized sounds.

FluidSynth works with a Creative Commons licensed SoundFont named FluidR3_GM. This SoundFont is good for general MIDI music playback.

MuseScore

Another source of freely available SoundFonts is the MuseScore project. The MuseScore Handbook: SoundFonts and SFZ Files page includes links to download various SF2 and SF3 SoundFonts that have been used in various versions of MuseScore.

Online Resources

Because SoundFonts have been around for decades searching the web for terms such as “SoundFont”, “SF2” or more specific terms like “soundfont sf2 piano” usually leads to many results. SoundFonts found online may be of random quality and may or may not be licensed in a way that suites your use of the download (be careful).

For specific instruments a good resource is the Polyphone Soundfonts page. This page lets you browse by instrument category and see reviews and comments by users.

There are many lists of SoundFont downloads online. One resource is SynthFont Links.

Compressing SoundFonts

This package also supports compressed SoundFont2 formats .sf3 and .sfo. The compressed formats are similar to regular .sf2 but the audio waveforms are stored with Ogg/Vorbis compression instead of being stored uncompressed. This is especially useful for large General MIDI soundbanks that contain many instruments in one file. For information about converting SoundFonts see SFOTool.

Compressed streams are decompressed into memory when the file is loaded. This means there will be more computation required when loading the instrument. This also means the total memory needed at runtime will not be less than the equivalent uncompressed .sf2 version. The compressed format is more for saving space when distributing or storing the instrument file.

Another consideration is unused samples and instruments. It may make sense for your application to start with a large General MIDI SoundFont and then edit it to only include the instruments and sounds you actually use. The application Polyphone can be used to edit SoundFonts. The application cannot edit .sfo format, so you should use SFOTool to compress the SoundFont after editing with Polyphone.

Examples

Play a note

To play a note and hear it, the general steps are:

  1. Create a Synth object

  2. Call Synth.sfload() to load a SoundFont instrument

  3. Select a specific bank/preset in the instrument with Synth.program_select()

  4. Turn on a note with Synth.noteon()

  5. Start the audio callback thread with Synth.start()

  6. Wait to exit your program, maybe call time.sleep()

There is some flexibility in the order of steps. For example you could start the audio callback thread first and then select bank/preset and turn on the note.

Here is a tiny example program to play a note:


import tinysoundfont
import time

synth = tinysoundfont.Synth()
sfid = synth.sfload("florestan-piano.sf2")
synth.program_select(0, sfid, 0, 0)
synth.start()
synth.noteon(0, 48, 100)
time.sleep(1.0)

Note

This example requires having the SoundFont file florestan-piano.sf2 available. The example code assumes it is located in the current directory.

Play a Chord

One SoundFont instrument can play multiple notes at the same time in the same MIDI channel. To play a chord call Synth.noteon() for multiple keys.

Here is an example that plays a single chord.


import tinysoundfont
import time

synth = tinysoundfont.Synth()
sfid = synth.sfload("florestan-piano.sf2")
synth.program_select(0, sfid, 0, 0)
synth.start()

time.sleep(0.5)

synth.noteon(0, 48, 100)
synth.noteon(0, 52, 100)
synth.noteon(0, 55, 100)

time.sleep(0.5)

synth.noteoff(0, 48)
synth.noteoff(0, 52)
synth.noteoff(0, 55)

time.sleep(0.5)

Note

This example uses time.sleep() for scheduling Synth.noteon() and Synth.noteoff() calls. This way of scheduling events blocks the main thread and makes the program non-interactive. The timing resolution of this method is also limited. Changes to the Synth object will only affect audio output at audio buffer boundaries.

Note

This example requires having the SoundFont file florestan-piano.sf2 available. The example code assumes it is located in the current directory.

Change Instruments

One SoundFont can contain many instruments. This example shows playing notes from different instruments in the florestan-subset.sfo compressed SoundFont.


import tinysoundfont
import time

synth = tinysoundfont.Synth()
sfid = synth.sfload("florestan-subset.sfo")
synth.program_select(0, sfid, 0, 12) # Marimba
synth.program_select(1, sfid, 0, 45) # PizzicatoStr
synth.program_select(2, sfid, 0, 24) # Nylon-str.Gt
synth.start()

time.sleep(0.5)
synth.noteon(0, 60, 100)
synth.noteon(0, 67, 100)
time.sleep(0.5)
synth.noteon(1, 60, 100)
time.sleep(0.5)
synth.noteon(2, 70, 75)
time.sleep(0.5 / 8)
synth.noteoff(2, 70)
synth.noteon(2, 67, 75)

time.sleep(1.0)
synth.noteoff(0, 60)
synth.noteoff(0, 67)
synth.noteoff(1, 60)
synth.noteoff(2, 67)

time.sleep(1.0)

Note

This example uses time.sleep() for scheduling Synth.noteon() and Synth.noteoff() calls. This way of scheduling events blocks the main thread and makes the program non-interactive. The timing resolution of this method is also limited. Changes to the Synth object will only affect audio output at audio buffer boundaries.

Note

This example requires having the SoundFont file florestan-subset.sfo available. The example code assumes it is located in the current directory.

Play a MIDI File

This example plays a MIDI file using a General MIDI SoundFont. It schedules the song to play then waits until the song is finished and ends.


import tinysoundfont
import time

synth = tinysoundfont.Synth()
sfid = synth.sfload("FluidR3_GM.sf2")

seq = tinysoundfont.Sequencer(synth)
seq.midi_load("1080-c01.mid")

# Larger buffer because latency is not important
synth.start(buffer_size=4096)

while not seq.is_empty():
    time.sleep(0.5)

Note

This example requires downloading the FluidR3_GM SoundFont. The example code assumes the extracted SoundFont file is located in the current directory.

Note

This example requires having the MIDI file 1080-c01.mid available. The example code assumes it is located in the current directory.

Filter MIDI Events

This example plays a MIDI file using the florestan_subset.sfo compressed example SoundFont. It filters the MIDI instrument changes in the multi-channel song to only use preset 19 (Church Organ) for all channels.

The MIDI file uses MIDI GM presets 40 to 43 (or presets 41 through 44 in 1-based indexing). The florestan_subset.sfo SoundFont only contains preset 40. Without any MIDI filtering the playback would start with violin but later parts using cello and contrabass would default to a piano sound.


import tinysoundfont
import time

synth = tinysoundfont.Synth()
sfid = synth.sfload("florestan-subset.sfo")

def filter_program_change(event):
    """Make all program changes go to preset 19"""
    match event.action:
        case tinysoundfont.midi.ProgramChange(program):
            event.action.program = 19 # Church organ

seq = tinysoundfont.Sequencer(synth)
seq.midi_load("1080-c01.mid", filter=filter_program_change)

synth.start(buffer_size=4096)
while not seq.is_empty():
    time.sleep(0.5)

Note

This example requires having the SoundFont file florestan-subset.sfo available. The example code assumes it is located in the current directory.

Note

This example requires having the MIDI file 1080-c01.mid available. The example code assumes it is located in the current directory.

Drum Sounds

The General MIDI convention is that drum events happens on channel 10. You can set any channel to be a drum kit channel using Synth.program_select() or Synth.program_change(). A drum kit channel maps each MIDI key of the channel to different drum instruments. For example key 36 is a bass drum and 56 is a cowbell.

This example plays a short drum pattern.


import tinysoundfont
import time

synth = tinysoundfont.Synth()
sfid = synth.sfload("FluidR3_GM.sf2")
synth.program_select(0, sfid, 0, 0, True)

seq = tinysoundfont.Sequencer(synth)
seq.midi_load("drum.mid")
synth.start(buffer_size=4096)

while not seq.is_empty():
    time.sleep(0.5)
time.sleep(2.0)

Note

This example requires downloading the FluidR3_GM SoundFont. The example code assumes the extracted SoundFont file is located in the current directory.

Note

This example requires having the MIDI file drum.mid available. The example code assumes it is located in the current directory.

Topics

Command Line Tool

The tinysoundfont package contains a simple command line tool that can be useful for finding presets, testing the validity of SoundFonts, and playing MIDI files.

Here is an example that loads the demo SoundFont and shows the presets it defines:

python -m tinysoundfont --info florestan-subset.sfo

(In Windows you may need to use py instead of python).

This results in:

Info for SoundFont florestan-subset.sfo
0 - 2 : Piano
0 - 10 : Music Box
0 - 12 : Marimba
0 - 19 : Church Org.1
0 - 21 : Accordion Fr
0 - 24 : Nylon-str.Gt
0 - 38 : Synth Bass 1
0 - 40 : Violin
0 - 45 : PizzicatoStr
0 - 55 : OrchestraHit
0 - 61 : Brass 1
0 - 75 : Pan Flute
0 - 87 : Bass & Lead
0 - 90 : Polysynth
0 - 97 : Soundtrack
0 - 109 : Bagpipe
0 - 116 : Taiko

The output format shows bank - preset : Name.

Here is an example that plays a test note using preset 55:

python -m tinysoundfont --test florestan-subset.sfo --preset 55 --key 70

Here is an example that plays a MIDI file using the FluidR3_GM SoundFont:

python -m tinysoundfont --play FluidR3_GM.sf2 1080-c01.mid

Latency

If you don’t require low latency it is recommended to set the buffer_size passed to Synth.start() to a somewhat large value such as 4096 to avoid glitches.

If you do require low latency response you can decrease the buffer_size argument. A value of 1024 is usually safe for most platforms. Lower values can be safe but may result in glitches and audio underruns in some situations. Passing a value of 0 lets PortAudio decide on the buffer size to minimize latency. This generally works well if the main thread has priority. It may be too aggressive if other applications are running and interacting with the user.

One thing to note about buffer sizes and latency is that MIDI events that are scheduled by Sequencer can happen at precise positions in the audio output and will not have any jitter or timing issues. Direct calls to Synth to perform actions can only happen at audio buffer boundaries. This means that the larger the audio buffer the more timing jitter will happen for direct calls to Synth.

Too Loud / Too Quiet

SoundFonts have many internal settings which control gain and volume. Different SoundFonts may be adjusted to different expected final sound volumes. If you are getting sound output that is much too loud or too quiet you should adjust the gain. You can adjust global gain when constructing the Synth object. If you are loading several SoundFonts you can adjust the relative gain of each one when calling Synth.sfload(). All the gain factors are measured in relative dB. So a value of 0 means no change, +3 means double the signal, -3 means divide the signal by a factor of 2.