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:
To be easy to install with as few native dependencies as possible
To allow for note generation and musical experimentation quickly with minimal code
To provide a consistent API
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
brew install portaudio
pip install pyaudio
sudo apt install python3-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:
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:
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:
Create a
Synth
objectCall
Synth.sfload()
to load a SoundFont instrumentSelect a specific bank/preset in the instrument with
Synth.program_select()
Turn on a note with
Synth.noteon()
Start the audio callback thread with
Synth.start()
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.