Rainbow-Dreamer / sf2_loader

This is an easy-to-use soundfonts loader, player and audio renderer in python
GNU Lesser General Public License v2.1
36 stars 8 forks source link
music python sf2 soundfont soundfonts

sf2_loader

This is an easy-to-use soundfonts loader, player and audio renderer in python.

This is probably the most handy soundfont loader, player and renderer via pure programming at the time I am writing now (2021/8/29). This is a python package for handling SoundFont files, it has the following functionality:

Contents

Introduction

This sf2 loader is heavily combined with musicpy, which is one of my most popular project, focusing on music programming and music analysis and composition. If you have already learned how to use musicpy to build notes, chords and pieces, you can straightly pass them to the sf2 loader and let it play what you write. Besides of playing music with the loaded soundfonts files, I also write an audio renderer in the sf2 loader, which could render the audio from the loaded soundfont files with the input musicpy data structures and output as audio files, you can choose the output format, such as wav, mp3, ogg, and output file names, sample width, frame rate, channels and so on. In fact, this project borns with my attempt at making muscipy's daw module being able to load soundfont files to play and export audio files.

If you are not familiar with musicpy data structures and is not willing to learn it in recent times, you can also straightly using MIDI files as input to the sf2 loader, and export the rendered audio files using the loaded soundfont files. However, I still recommend you to learn about musicpy, even not to consider music programming and analysis, it could also be very useful for MIDI files editing and reconstructing.

This sf2 loader is compatible with both 32-bit and 64-bit python versions, for python >= 3.6, so be sure your installed python version match the requirements for this package to use.

This package is currently being tested on Windows, Linux and macOS. For Windows version, this package is tested on Windows 10.

Update: (2021/12/3) After many updates, currently the latest version is all compatible with Windows, Linux and macOS, so no compatible version is needed anymore, on Linux and macOS you just need to pip install sf2 loader the same as in Windows, and then you need to install fluidsynth and ffmpeg separately on Linux and macOS, the installation instruction is updated.

Update: (2021/9/5) The macOS compatible version is ready, the installation and configuration of Linux compatible version is at the installation section of this readme. This macOS compatible version of sf2_loader is tested on Catalina 10.15.5.

Update: (2021/9/5) The Linux compatible version is ready, the installation and configuration of Linux compatible version is at the installation section of this readme. This Linux compatible version of sf2_loader is tested on Ubuntu 18.04.5.

Important note 1: the required python package musicpy is updated very frequently, so please regularly update musicpy by running

pip install --upgrade musicpy

in cmd/terminal.

Important note 2: If you cannot hear any sound when running the play functions, this is because some IDE won't wait till the pygame's playback ends, they will stops the whole process after all of the code are executed without waiting for the playback. You can set wait=True in the parameter of the play functions, which will block the function till the playback ends, so you can hear the sounds.

Installation

Windows

You can use pip to install this sf2 loader.

Run this line in cmd/terminal to install.

pip install sf2_loader

Note: This package uses pydub as a required python package, which requires ffmpeg or libav installed to have abilities to deal with non-wav files (like mp3, ogg files), so I strongly recommend to install ffmpeg/libav and configure it correctly to make pydub working perfectly. You can refer to this link which is pydub's github main page readme to see how to do it, or you can follow the steps I provide here, which is easier and faster.

Firstly, download the ffmpeg zip file from this link, this is from the release page of musicpy which requires ffmpeg for the musicpy's daw module.

Then, unzip the folder ffmpeg from the zip file, put the folder in C:\

Then, add the path C:\\ffmpeg\\bin to the system environment variable PATH.

Finally, restart the computer.

Now you are all done with the set up of ffmpeg for pydub. If there are no warnings about ffmpeg from pydub pop up after you import this package, then you are good to go.

Linux

You can use pip to install this sf2 loader, which is the same as in Windows.

Then, there are some important and necessary steps to configure this package in order to use it on Linux:

Firstly, you need to install fluidsynth on Linux, you can refer to this link to see how to install fluidsynth on different Linux systems. Here I will put the install command for Ubuntu and Debian:

sudo apt-get install fluidsynth

Run this command in terminal on Ubuntu or Debian, and waiting for fluidsynth to finish installing.

Secondly, you need to install ffmpeg on Linux (the same reason as in Windows), you can just run this command in terminal to install ffmpeg on Linux:

sudo apt-get install ffmpeg libavcodec-extra

macOS

You can use pip to install this sf2 loader, which is the same as in Windows.

Then, there are some important and necessary steps to configure this package in order to use it on macOS:

Firstly, you need to install fluidsynth on macOS, the easiest way to install ffmpeg in macOS is using homebrew. You need to make sure you have installed homebrew in macOS first, and then run brew install fluidsynth in terminal, and waiting for fluidsynth to be installed.

If you haven't installed homebrew before and cannot find a good way to install homebrew, here I will provide a very easy way to install homebrew on macOS, thanks from Ferenc Yim's answer from this Stack Overflow question:

open this link in your browser, right-click and save it to your computer, and then open a terminal and run it with:
/bin/bash path-to/install.sh, and waiting for homebrew to be installed.

Secondly, you need to install ffmpeg on macOS (the same reason as in Windows), you can just run this command in terminal to install ffmpeg on macOS using homebrew:

brew install ffmpeg

Usage

Here are the syntax for the most important functionalities of this sf2 loader.

Firstly, you can import this sf2 loader using this line:

import sf2_loader as sf

or you can using this line, which is without namespace, but this is not recommended in big projects because of potential naming conflicts with other python packages you import.

from sf2_loader import *

Here we will use the first way of import as the standard.

When you install sf2_loader, the musicpy package will be installed at the same time. The musicpy package is imported in sf2_loader as mp, so you can use musicpy package straightly by calling sf.mp.

Initialize a soundfont loader

To initialize a soundfont loader, you can pass a soundfont file path to the class sf2_loader, or leave it as empty, and use load function of sf2_loader to load soundfont files later.

loader = sf.sf2_loader(soundfont_file_path)
# or
loader = sf.sf2_loader()

# examples
loader = sf.sf2_loader(r'C:\Users\Administrator\Desktop\celeste.sf2')
# or
loader = sf.sf2_loader('C:/Users/Administrator/Desktop/celeste.sf2')

Load sondfont files

You can load a soundfont file when you initialize a sf2_loader by passing a soundfont file path to the initialize function, or use load function of the sf2 loader to load new soundfont files into the sf2 loader.

Each time you load a soundfont file, the sf2 loader will save the soundfont file name and the soundfont id, you can get and use them by calling the attributes file and sfid_list of the sf2 loader.

You can unload a loaded soundfont file by index (1-based) using unload function of the sf2 loader.

loader = sf.sf2_loader(soundfont_file_path)
loader.load(soundfont_file_path2)

# examples
loader = sf.sf2_loader(r'C:\Users\Administrator\Desktop\celeste.sf2')
loader.load(r'C:\Users\Administrator\Desktop\celeste2.sf2')

>>> loader.file
['C:\\Users\\Administrator\\Desktop\\celeste.sf2', 'C:\\Users\\Administrator\\Desktop\\celeste2.sf2']

# if the soundfont file does not exist in the given file path, the soundfont id will be -1

>>> loader.sfid_list
[1, 2]

loader.unload(2) # unload the second loaded soundfont files of the sf2 loader

>>> loader.file
['C:\\Users\\Administrator\\Desktop\\celeste.sf2']

The representation of the soundfont loader

You can print the sf2 loader and get the information that the sf2 loader currently has.

The channel number, bank number and preset number are 0-based, the soundfont id is 1-based.

>>> loader
[soundfont loader]
loaded soundfonts: ['C:\\Users\\Administrator\\Desktop\\celeste.sf2', 'C:\\Users\\Administrator\\Desktop\\celeste2.sf2']
soundfonts id: [1, 2]
current channel: 0
current soundfont id: 1
current soundfont name: celeste.sf2
current bank number: 0
current preset number: 0
current preset name: Stereo Grand

Change current channel, soundfont id, bank number and preset number

Each channel of the sf2 loader has 3 attributes, which are SoundFont id, bank number and preset number. The sf2 loader has a attribute current_channel which is used when displaying current information of current channel.

You can use the change function of the sf2 loader to change either one or some or all of the current channel, soundfont id, bank number and preset number of the sf2 loader. You can use either preset number or preset name to change current preset of a channel.

There are also some syntactic sugar I added for this sf2 loader, which is very convenient in many cases.

For example, you can use loader < preset to change the current preset number of the sf2 loader to change the instrument of the soundfont files that the sf2 loader will use to play and export, while current channel, soundfont id and bank number remain unchanged. This syntactic sugar also accept second parameter as the bank number, which is used as loader < (preset, bank).

You can also use loader % channel to change current channel.

There are also a change function for each attribute of current channel, soundfont id, bank number and preset number, namely change_channel, change_sfid, change_bank, change_preset.

Each change function except change_channel has an optional argument channel to specify change which channel's attribute, if not specified, then change current channel's attribute by default.

loader.change(channel=None,
              sfid=None,
              bank=None,
              preset=None,
              correct=True,
              hide_warnings=True,
              mode=0)
# If you only need to change one or some of the attributes,
# you can just specify the parameters you want to change,
# the unspecified parameters will remain unchanged.

# correct: if you set it to True,
# when the given parameters cannot find any valid instruments,
# the sf2 loader will go back to the program before the change,
# if you set it to False, the program will be forced to change to the
# given parameters regardless of whether the sf2 loader can find any valid
# instruments or not

# hide_warnings: prevent warning messages from external C/C++ libraries printed to the terminal or not

# mode: if set to 0, then when channel is specified, the current channel of the sf2 loader will be changed
# to that channel, and then change other specified attributes, otherwise,
# change other attributes within the specified channel,
# but current channel of the sf2 loader remain unchanged

# examples
loader.change(preset=2) # change current preset number to 2
loader.change(preset='Strings') # change current preset to Strings
loader.change(bank=9, preset=3) # change current bank number to 9 and current preset number to 3
loader.change_preset(2) # change current preset number to 2
loader.change_preset('Strings') # change current preset to Strings
loader.change_bank(9) # change current bank number to 9
loader.change_bank(9, channel=1) # change current bank number of channel 1 to 9
loader.change_channel(2) # change current channel to 2
loader.change_sfid(2) # change current soundfont id to 2
loader.change_soundfont('celeste2.sf2')
# change current soundfont file to celeste2.sf2, the parameter could be full path or
# just the file name of the soundfont file, but it must be loaded in
# the sf2 loader already
loader < 2 # change current preset number to 2
loader < 'Strings' # change current preset to Strings
loader < (3, 9) # change current bank number to 9, current preset number to 3
loader < ('Strings', 9) # change current bank number to 9, current preset to Strings
loader % 1 # change current channel to 1

About channel initialization

Note that when a sf2 loader is initialized, the channel 0 will be automatically initialized, but other channels are not initialized. If you use a channel that is not initialized to play a sound or render audio, you will get no sound, which is the same for a channel that is initialized but with an invalid preset. But there are automatic initialization of channels built in this sf2 loader to make things easier.

When you change to a channel that is not initialized, the program will automatically initialize that channel by select the first loaded SoundFont id to that channel and select the first valid instrument in bank 0 (or bank 128 for channel 9). If no valid instrument is found in bank 0 (or bank 128 for channel 9), then that channel will be initialized with no valid current preset, you will need to select a valid preset for that channel by looking at the result of the function all_instruments which returns all of the available banks and presets in current SoundFont file, I will talk about this function later.

The automatic initialization of channels that are not initialized will also take place when you trying to play or export a piece instance of musicpy or MIDI files using channels that are not initialized (not for playing or exporting note and chord). However, there is at least one situation that you will still get no sound with uninitialized channels, that is when the automatic initialization of a channel cannot find a valid preset of the initial bank number, then it will remain initialized but with no valid current preset. In this case, you will need to select a valid preset for that channel yourself in order to get sound when playing or exporting using that channel.

If you want to manually initialize a channel, you can use init_channel function of sf2 loader, which takes a paremeter channel.

The initial bank number is 0 for each channel except channel 9, which is 128, since channel 9 is for percussion as default. The initial preset number for each channel is 0, but this might not be the first valid preset number for current SoundFont id and current bank number. The initial SoundFont id for a channel that is not initialized is 0, if a channel is initialized, then it will have a SoundFont id that is >= 1. You can use this information to check if a channel is already initialized or not. To get current SoundFont id of a channel, use get_sfid function of sf2 loader, which I will talk about later, or you can use valid_channel function of sf2 loader to check if a channel is already initialized or not.

You can initialize channels as much as you can in this sf2 loader, which could be more than 16 channels (the restriction of MIDI 1.0). But since most MIDI files out there are at most 16 channels, so this advantage does not actually works well if you directly use MIDI files for this sf2 loader. If you use export_piece function to export a piece instance of musicpy to audio files, the number of channels and tracks of a piece instance could be more than 16, and they will be successfully rendered to audio. If you are interested in this, you can check out the piece data structure in the wiki of musicpy.

To reset the current configuration of all channels, you can use reset_all_channels function of sf2 loader, which will reset all channels to uninitialized state.

Get the instrument names

If you want to get the instrument names of the soundfont files you load in the sf2 loader, you can use get_all_instrument_names function of the sf2 loader, which will give you a list of instrument names that current soundfont file's current bank number has (or you can specify them), with given maximum number of preset number to try, start from 0. By default, the maximum number of the preset number to try is 128, which is from 0 to 127. If you want to get the exact preset numbers for all of the instrument names in current bank number, you can set the parameter get_ind to True.

loader.get_all_instrument_names(sfid=None,
                                bank=None,
                                max_num=128,
                                get_ind=False,
                                mode=0,
                                return_mode=0,
                                hide_warnings=True)

# mode: when get_ind is True, if mode is 1, the current preset number will be set to the first available
# instrument in the current bank number

# return_mode: if it is 0, then when get_ind is set to True, this function
# will return a dictionary which key is the preset number, value is the 
# corresponding instrument name; if it is 1, then when get_ind is set to True,
# this function will return a tuple of 2 elements, which first element is
# a list of instrument names and second element is a list of the
# corresponding preset numbers

If you want to get all of the instrument names of all of the available banks of the soundfont files you load in the sf2 loader, you can use all_instruments function of the sf2 loader, which will give you a dictionary which key is the available bank number, value is a dictionary of the presets of this bank, which key is the preset number and value is the instrument name. You can specify the maximum of bank number and preset number to try, the default value of maximum bank number to try is 129, which is from 0 to 128, the default value of maximum preset number for each bank to try is 128, which is from 0 to 127. You can also specify the soundfont id to get all of the instrument names of a specific soundfont file you loaded, in case you have loaded multiple soundfont files in the sf2 loader.

loader.all_instruments(max_bank=129, max_preset=128, sfid=None, hide_warnings=True)

# max_bank: the maximum bank number to try,
# the default value is 129, which is from 0 to 128

# max_preset: the maximum preset number to try,
# the default value is 128, which is from 0 to 127

# sfid: you can specify the soundfont id to get the instrument names
# of the soundfont file with the soundfont id

To get the instrument name of a given soundfont id, bank number and preset number, you can use get_instrument_name function.

loader.get_instrument_name(sfid=None,
                           bank=None,
                           preset=None,
                           hide_warnings=True)

To get current instrument name, you can use get_current_instrument function.

loader.get_current_instrument()

To get current soundfont id, bank number and preset number of a given channel, you can use channel_info function, which returns a tuple (sfid, bank, preset).

To get one of current SoundFont id, current bank number, current preset number and current preset name of a channel, you can use functions get_sfid, get_bank, get_preset, get_preset_name, which all takes a parameter channel, which has a default value None, if the channel parameter is None, then use current channel. For the function get_preset_name, if the channel has not yet initialized, it will return None.

loader.channel_info(channel=None)

# channel: if channel is None, then returns the channel info of current channel

loader.get_sfid() # get current channel's SoundFont id
loader.get_bank(1) # get current bank number of channel 1
loader.get_preset(1) # get current preset number of channel 1
loader.get_preset_name(1) # get current preset name of channel 1

Here is an example of getting all of the instrument names in current bank number.

>>> loader.get_all_instrument_names()
['Stereo Grand', 'Bright Yamaha Grand', 'Electric Piano', 'Honky-tonk EMU2', 'Electric Piano 1', 'Legend EP 2', 'St.Harpsichd_Lite', 'Clavinet', 'Celesta', 'Glockenspiel', 'Music Box', 'VivesPS06', 'prc:Marimba', 'Xylophone', 'Tubular Bells', 'Dulcimer', 'DrawbarOrgan', 'PercOrganSinkla', 'Rock Organ', 'Church Organ', 'Reed Organ', 'Accordian', 'Harmonica', 'Bandoneon', 'TyrosNylonLight', 'Steel Guitar', 'Jazz Gt', 'Dry Clean Guitar', 'Palm Muted Guitar', 'Garcia _0_29', 'Les Sus_0_30', 'Guitar Harmonic', 'Acoustic Bass', 'MM JZ.F_0_33', 'BassPick&Mutes', 'Fretless Bass', 'Slap Bass 1', 'Slap Bass 2', 'Synth Bass 1', 'Synth Bass 2', 'Violin', 'Viola', 'Cello', 'Contrabass', 'Tremolo', 'Pizzicato Section', 'ClavinovaHarp', 'Timpani', 'Strings Orchest', 'Slow Strings', 'Synth Strings 1', 'Synth Strings 2', 'Ahh Choir', 'Ohh Voices', 'SynVoxUT', 'Orchestra Hit', 'Romantic Tp', 'Solo Bo_0_57', 'Tuba', 'Sweet Muted', 'FH LONG_0_60', 'BRASS', 'AccesVirusBrass', 'Synth B_0_63', 'Soprano Sax', 'Altsoft vib', 'Blow Tenor', 'Bari Sax', 'Oboe', 'English Horn', 'Bassoon', 'Clarinet', 'Piccolo', 'Flute', 'Recorder', 'ClavinovaPanFlu', 'Bottle Blow', 'Shakuhachi', 'Whistle', 'Ocarina', 'Square Wave', 'Saw Wave', 'Calliope Lead', 'Chiffer Lead', 'Charang', 'Solo Vox', 'Fifth Sawtooth Wave', 'Bass & Lead', 'Fantasia', 'Warm Pad', 'Polysynth', 'Space Voice', 'Bowed Glass', 'Metal Pad', 'Halo Pad', 'Sweep Pad', 'Ice Rain', 'Soundtrack', 'Crystal', 'Atmosphere', 'Brightness', 'Goblin', 'Echo Drops', 'Star Theme', 'Sitar', 'Banjo', 'Shamisen', 'Koto', 'Kalimba', 'BagPipe', 'Fiddle', 'Shenai', 'Tinker Bell', 'Agogo', 'Steel Drums', 'Woodblock', 'Taiko Drum', 'Melodic Tom', 'Synth Drum', 'Reverse Cymbal', 'Fret Noise', 'Breath Noise', 'Sea Shore', 'Bird Tweet', 'Telephone', 'Helicopter', 'Applause', 'Gun Shot']

Here is an example of getting all of the instrument names of all of the available banks.

>>> loader.all_instruments()
{0: {0: 'Grand Piano', 1: 'Bright Piano', 2: 'Rock Piano', 3: 'Honky-Tonk Piano', 4: 'Electric Piano', 5: 'Crystal Piano', 6: 'Harpsichord', 7: 'Clavinet', 8: 'Celesta', 9: 'Glockenspiel', 10: 'Music Box', 11: 'Vibraphone', 12: 'Marimba', 13: 'Xylophone', 14: 'Tubular Bells', 15: 'Dulcimer (Santur)', 16: 'DrawBar Organ', 17: 'Percussive Organ', 18: 'Rock Organ', 19: 'Church Organ', 20: 'Reed Organ', 21: 'Accordion', 22: 'Harmonica', 23: 'Bandoneon', 24: 'Nylon Guitar', 25: 'Steel String Guitar', 26: 'Jazz Guitar', 27: 'Clean Guitar', 28: 'Muted Guitar', 29: 'Overdrive Guitar', 30: 'Distortion Guitar', 31: 'Guitar Harmonics', 32: 'Acoustic Bass', 33: 'Fingered Bass', 34: 'Picked Bass', 35: 'Fretless Bass', 36: 'Slap Bass 1', 37: 'Slap Bass 2', 38: 'Synth Bass 1', 39: 'Synth Bass 2', 40: 'Violin', 41: 'Viola', 42: 'Cello', 43: 'ContraBass', 44: 'Tremolo Strings', 45: 'Pizzicato Strings', 46: 'Orchestral Harp', 47: 'Timpani', 48: 'Strings Ensemble 1', 49: 'Strings Ensemble 2', 50: 'Synth Strings 1', 51: 'Synth Strings 2', 52: 'Choir Aahs', 53: 'Voice Oohs', 54: 'Synth Voice', 55: 'Orchestra Hit', 56: 'Trumpet', 57: 'Trombone', 58: 'Tuba', 59: 'Muted Trumpet', 60: 'French Horns', 61: 'Brass Section', 62: 'Synth Brass 1', 63: 'Synth Brass 2', 64: 'Soprano Sax', 65: 'Alto Sax', 66: 'Tenor Sax', 67: 'Baritone Sax', 68: 'Oboe', 69: 'English Horns', 70: 'Bassoon', 71: 'Clarinet', 72: 'Piccolo', 73: 'Flute', 74: 'Recorder', 75: 'Pan Flute', 76: 'Blown Bottle', 77: 'Shakuhachi', 78: 'Whistle', 79: 'Ocarina', 80: 'Square Wave', 81: 'Saw Wave', 82: 'Synth Calliope', 83: 'Chiffer Lead', 84: 'Charang', 85: 'Solo Voice', 86: '5th Saw Wave', 87: 'Bass & Lead', 88: 'Fantasia (New Age)', 89: 'Warm Pad', 90: 'Poly Synth', 91: 'Space Voice', 92: 'Bowed Glass', 93: 'Metal Pad', 94: 'Halo Pad', 95: 'Sweep Pad', 96: 'Ice Rain', 97: 'Sound Track', 98: 'Crystal', 99: 'Atmosphere', 100: 'Brightness', 101: 'Goblin', 102: 'Echo Drops', 103: 'Star Theme', 104: 'Sitar', 105: 'Banjo', 106: 'Shamisen', 107: 'Koto', 108: 'Kalimba', 109: 'Bag Pipe', 110: 'Fiddle', 111: 'Shannai', 112: 'Tinkle Bell', 113: 'Agogo', 114: 'Steel Drums', 115: 'Wood Block', 116: 'Taiko Drum', 117: 'Melodic Tom', 118: 'Synth Drum', 119: 'Reverse Cymbal', 120: 'Guitar Fret Noise', 121: 'Breath Noise', 122: 'Sea Shore', 123: 'Bird Tweets', 124: 'Telephone', 125: 'Helicopter', 126: 'Applause', 127: 'Gun Shot'}, 128: {0: 'Standard Drum Kit', 8: 'Room Drum Kit', 16: 'Power Drum Kit', 24: 'Electronic Drum Kit', 25: 'TR-808/909 Drum Kit', 32: 'Jazz Drum Kit', 40: 'Brush Drum Kit', 48: 'Orchestral Drum Kit', 49: 'Fix Room Drum Kit', 127: 'MT-32 Drum Kit'}}

Play notes, chords, pieces and MIDI files

You can use play_note function of the sf2 loader to play a note with specified pitch using current channel, soundfont id, bank number and preset number. The note could be a string representing a pitch (for example, C5) or a musicpy note instance. If you want to play the note by another instrument, you need to change current preset (and other program parameters if needed) before you use play_note function, the same goes for other play functions.

loader.play_note(note_name,
                 duration=2,
                 decay=1,
                 volume=100,
                 channel=0,
                 start_time=0,
                 sample_width=2,
                 channels=2,
                 frame_rate=44100,
                 name=None,
                 format='wav',
                 effects=None,
                 bpm=80,
                 export_args={},
                 wait=False)

# note_name: the name of the note, i.e. C5, D5, C (if the octave number
# is not specified, then the default octave number is 4), or musicpy note instance

# duration: the duration of the note in seconds

# decay: the decay time of the note in seconds

# volume: the volume of the note in MIDI velocity from 0 - 127

# channel: the channel to play the note

# start_time: the start time of the note in seconds

# sample_width: the sample width of the rendered audio

# channels: the number of channels of the rendered audio

# frame_rate: the frame rate of the rendered audio

# name: the file name of the exported audio file, this is only used in export_note function

# format: the audio file format of the exported audio file, this is only used in export_note function

# effects: audio effects you want to add to the rendered audio

# bpm: the BPM of the note

# export_args: a keyword dictionary, the other keyword arguments for exporting,
# you can refer to the keyword parameters of pydub's AudioSegment's export function,
# a useful situation is to specify the bitrate of the exported mp3 file
# to be exported (when you set the format parameter to 'mp3'), for example,
# we want to specify the bitrate to be 320Kbps,
# then this parameter could be {'bitrate': '320k

# wait: if set to True, wait till the playback ends

# examples
loader.play_note('C5') # play a note C5 using current instrument

# you will hear a note C5 playing using current instrument

loader < 25 # change to another instrument at preset number 25

loader.play_note('C5') # play a note C5 using the instrument we have changed to

# you will hear a note C5 playing using a new instrument

loader.play_note('D') # play a note without octave number specified, will play the note D4

loader.play_note(sf.mp.N('C5')) # play a note using musicpy note structure

loader.play_note('C5', duration=3) # play a note C5 for 3 seconds

You can use play_chord function of the sf2 loader to play a chord using current channel, soundfont id, bank number and preset number. The chord must be a musicpy chord instance.

loader.play_chord(current_chord,
                  decay=0.5,
                  channel=0,
                  start_time=0,
                  piece_start_time=0,
                  sample_width=2,
                  channels=2,
                  frame_rate=44100,
                  name=None,
                  format='wav',
                  bpm=80,
                  fixed_decay=True,
                  effects=None,
                  pan=None,
                  volume=None,
                  length=None,
                  extra_length=None,
                  export_args={},
                  wait=False)

# current_chord: musicpy chord instance

# decay: the decay time unit in seconds, each note's decay time will be calculated
# with decay * duration of the note, or if fixed_decay is True, this decay time
# will be applied to every note, this decay time could also be a list of each note's
# decay time

# channel - bpm: same as play_note

# piece_start_time: this is used when dealing with a musicpy piece instance, you won't need to set this generally

# fixed_decay: if this is set to True, the decay time will be applied to every note

# effects: same as play_note

# pan: the pan effects you want to add to the rendered audio

# volume: the volume effects you want to add to the rendered audio

# the pan and volume effects are corresponding to the MIDI CC messages

# length: you can specify the whole length of the rendered audio in seconds (used in case of audio effects)

# extra_length: you can specify the extra length of the rendered audio in seconds (used in case of audio effects)

# export_args: same as play_note

# wait: same as play_note

# examples
loader.play_chord(sf.mp.C('C')) # play a C major chord starts at C4 (default when
# no octave number is specified)

loader.play_chord(sf.mp.C('Cmaj7', 5)) # play a Cmaj7 chord starts at C5

You can use play_piece function of the sf2 loader to play a piece using current channel and soundfont id. The piece must be a musicpy piece instance. Here piece means a piece of music with multiple individual tracks with different instruments on each of them (it is also ok if you want some or all of the tracks has the same instruments). You can custom which instrument you want the soundfont to play for each track by setting the instruments attribute of the piece instance, instrument of a track of the piece instance could be preset or [preset, bank, (sfid)].

You can learn more about piece data structure here at musicpy wiki.

loader.play_piece(current_chord,
                  decay=0.5,
                  sample_width=2,
                  channels=2,
                  frame_rate=44100,
                  name=None,
                  format='wav',
                  fixed_decay=True,
                  effects=None,
                  clear_program_change=False,
                  length=None,
                  extra_length=None,
                  track_lengths=None,
                  track_extra_lengths=None,
                  export_args={},
                  show_msg=False,
                  wait=False)

# current_chord: musicpy piece instance

# decay: the decay time for the tracks of the piece instance (which is musicpy chord
# instance), note that if this decay time is a list,
# then it will be treated as the decay time for each track separately,
# otherwise it will be applied to each track. If you want to pass the same list
# to each track, you need to pass a list of lists which elements are identical.

# sample_width - effects: same as play_chord

# clear_program_change: when there are program change messages in the piece instance,
# the instruments are forced to change during rendering, so you cannot use the
# instrument you want to play, if you clear these messages, then you can specify
# which instruments you want to play

# length: you can specify the whole length of the rendered audio in seconds (used in case of audio effects)

# extra_length: you can specify the extra length of the rendered audio in seconds (used in case of audio effects)

# track_lengths: the length settings list of each track

# track_extra_lengths: the extra length settings list of each track

# export_args: same as play_note

# show_msg: if it is set to True, then when the sf2 loader is rendering a piece instance to audio, it will print some messages showing current process, such as `rendering track 1/16 ...` (rendering the first track of the total 16 tracks), the default value is False

# wait: same as play_note

# examples

# construct a musicpy piece instance and play it using the sf2 loader,
# here we have a chord progression from Cmaj7 to Fmaj7, with different instruments of each chord
current_piece = sf.mp.P(
    tracks=[
        # C function is to translate human-readable chord name to chord
        sf.mp.C('Cmaj7') % (1, 1 / 8) * 4,
        sf.mp.C('Fmaj7') % (1, 1 / 8) * 4,

        # The code below does exactly the same job
        # sf.mp.chord('C, E, G, B') % (1, 1 / 8) * 4,
        # sf.mp.chord('F, A, C, E') % (1, 1 / 8) * 4
    ],
    instruments=[1, 47],
    start_times=[0, 2],
    bpm=150)
loader.play_piece(current_piece)

# read a MIDI file to a musicpy piece instance and play it using the sf2 loader
current_midi_file = sf.mp.read(midi_file_path)
loader.play_piece(current_midi_file)

You can use play_midi_file function of the sf2 loader to play a MIDI file using current channel and soundfont id. You can set the first parameter to the MIDI file path, and then the sf2 loader will read the MIDI file and analyze it into a musicpy piece instance, and then render it to audio data.

loader.play_midi_file(current_chord,
                      decay=0.5,
                      sample_width=2,
                      channels=2,
                      frame_rate=44100,
                      name=None,
                      format='wav',
                      fixed_decay=True,
                      effects=None,
                      clear_program_change=False,
                      instruments=None,
                      length=None,
                      extra_length=None,
                      track_lengths=None,
                      track_extra_lengths=None,
                      export_args={},
                      show_msg=False,
                      wait=False,
                      **read_args)

# current_chord: the MIDI file path

# decay - clear_program_change: same as play_piece

# instruments: the list of the instruments you want to play, the sf2 loader
# will use this instrument list instead of the instrument settings in the MIDI file,
# note that this instruments list must be the same length as the number of tracks
# of the MIDI file

# length - show_msg: same as play_piece

# wait: same as play_note

# **read_args: this is the keyword arguments for the musicpy read function

# examples

# play a MIDI file given a file path using current soundfont file
loader.play_midi_file(r'C:\Users\Administrator\Desktop\test.mid')

loader.change_soundfont('celeste2.sf2') # change to another loaded soundfont file

# play a MIDI file given a file path using another soundfont file
loader.play_midi_file(r'C:\Users\Administrator\Desktop\test.mid')

# you can also specify which channel use which soundfont files in the instruments
# parameter by specifying the soundfont id

You can specify which bank and preset (including channel and sfid) that each track of the MIDI file uses by setting the instruments parameter of the play_midi_file function.

Export notes, chords, pieces and MIDI files

You can export notes, chords, pieces and MIDI files using loaded soundfont files in the sf2 loader using export_note, export_chord, export_piece, export_midi_file function of the sf2 loader.

All of the parameters of these export functions can refer to their corresponding play functions, except a parameter get_audio, if this parameter is set to True, then the export functions will return an AudioSegment instance (this is an audio instance in pydub) which contains raw audio data for further audio process. If this parameter is set to False (which is default), then the export functions will export the rendered audio data to an audio file with the file name and the audio file format you specify.

# examples

# render a MIDI file with current soundfont files and export as a mp3 file 'test.mp3'
loader.export_midi_file(r'C:\Users\Administrator\Desktop\test.mid', name='test.mp3', format='mp3')

# if you want to specify the bitrate of the exported mp3 file to be 320Kbps
loader.export_midi_file(r'C:\Users\Administrator\Desktop\test.mid', name='test.mp3', format='mp3', export_args={'bitrate': '320k'})

Export instruments

You can export instruments of a specified instrument of the loaded soundfont files using export_instruments function of the sf2 loader.

You can specify the pitch range of the notes, the default is from A0 to C8, which is the most common 88 keys on the piano.

The duration of the notes is 6 seconds by default, you can set the duration of the notes in the function.

The format of the export audio files is wav by default, you can set the export audio file format in the function.

The exported audio file name of each note will be in the format pitch.format by default, where pitch is the note name such as C5, format is the audio file format you specify such as wav, so for example, the exported audio file names of notes will be like C5.wav, C#5.wav, D5.wav. You can custom the name format of each note with the parameter name in the function, it could either be a list of each note's name (without file extension) or a function to format each note's name (without file extension).

loader.export_instruments(channel=None,
                          sfid=None,
                          bank=None,
                          preset=None,
                          start='A0',
                          stop='C8',
                          duration=6,
                          decay=1,
                          volume=127,
                          sample_width=2,
                          channels=2,
                          frame_rate=44100,
                          format='wav',
                          folder_name='Untitled',
                          effects=None,
                          bpm=80,
                          name=None,
                          show_full_path=False,
                          export_args={})

# channel, sfid, bank, preset: use which instrument to play, you can refer to
# change function

# start, stop: the pitch range, note that these must be strings representing pitches

# duration - format: refer to play_note function

# folder_name: the folder name of the exported instruments

# effects: refer to play_note function, will be applied to each note

# bpm: refer to play_note function

# name: if not None, then set each note's file name with this parameter,
# this could be a list of each note's name (without file extension) or a function
# to format each note's name (without file extension),
# for example, ['C5', 'C#5' 'D5'], lambda s: f'piano{s}'

# show_full_path: when this function is running, it will print the export messages
# for each note, showing which soundfont file and which bank and preset
# you use to export which note, if this is set to True,
# then the file path of the soundfont file will be full path,
# otherwise the file path will be only the file name

# export_args: same as play_note

# examples

# export notes from A0 to C8 of current instrument
loader.export_instruments()

# export notes from C5 to C6 of current instrument
loader.export_instruments(start='C5', stop='C6')

# export notes from A0 to C8 of current instrument of
# the loaded soundfont file with id 2
# (or you can change current soundfont file in advance)
loader.export_instruments(sfid=2)

Audio effects

There is a effects parameter in the play and export functions, but how to use it?

This python package provides a class effect, which could store a type of audio effect, such as reverse, offset, fade in, fade out, ADSR envelope, and you can customize your own audio effect functions. You can use effect class to package an audio effect function and put it as an element in a list, the list could be used as the value of effects in the play and export functions. There are already some predefined effect instances such as reverse, fade, adsr.

For more details and usages of effect class, and the parameters of predefined audio effects, please refer to the daw module of musicpy, here is the link for the documentation of the audio effects.

effect(func, name=None, *args, unknown_args=None, **kwargs)

# func: the function to process the audio effect, the first parameter must be a pydub AudioSegment instance, the other parameters could be customized as you like, the return value must be a pydub AudioSegment instance

# name: the name of the audio effect

# *args, **kwargs: the positional arguments and keyword arguments that the function to process the audio effect could receive

# unknow_args: the keyword arguments that the function to process the audio effect could receive, but cannot be known when packaged (for example, the bpm in real time)

# examples

# play a note C5 using current instrument with a reverse audio effect
loader.play_note('C5', effects=[reverse])

# play a note C5 using current instrument with a fade in audio effect of 2s
loader.play_note('C5', effects=[fade_in(duration=2000)])

# export a note C5 using current instrument with a reverse audio effect
loader.export_note('C5', effects=[reverse])

Pause, unpause and stop current playing sounds

When you start playing sounds, you can use pause function of sf2 loader to pause current playing, unpause function to unpause, stop function to stop.

Donation

This project is developed by Rainbow Dreamer on his spare time. If you feel this project is useful to you and want to support it and it's future development, please consider buy me a coffee, I appreciate any amount.

Support via PayPal