Upload 2 files
Browse files- TMIDIX.py +129 -43
- midi_to_colab_audio.py +679 -223
TMIDIX.py
CHANGED
|
@@ -51,7 +51,7 @@ r'''############################################################################
|
|
| 51 |
|
| 52 |
###################################################################################
|
| 53 |
|
| 54 |
-
__version__ = "25.7.
|
| 55 |
|
| 56 |
print('=' * 70)
|
| 57 |
print('TMIDIX Python module')
|
|
@@ -3724,19 +3724,52 @@ def validate_pitches(chord, channel_to_check = 0, return_sorted = True):
|
|
| 3724 |
chord.sort(key = lambda x: x[4], reverse=True)
|
| 3725 |
return chord
|
| 3726 |
|
| 3727 |
-
def adjust_score_velocities(score,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3728 |
|
| 3729 |
-
|
| 3730 |
-
|
| 3731 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3732 |
|
| 3733 |
-
max_channel_velocity = max([c[5] for c in score])
|
| 3734 |
-
if max_channel_velocity < min_velocity:
|
| 3735 |
-
factor = max_velocity / min_velocity
|
| 3736 |
else:
|
| 3737 |
-
|
| 3738 |
-
|
| 3739 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3740 |
|
| 3741 |
def chordify_score(score,
|
| 3742 |
return_choridfied_score=True,
|
|
@@ -5029,54 +5062,101 @@ def patch_list_from_enhanced_score_notes(enhanced_score_notes,
|
|
| 5029 |
|
| 5030 |
###################################################################################
|
| 5031 |
|
| 5032 |
-
def patch_enhanced_score_notes(
|
| 5033 |
-
|
| 5034 |
-
|
| 5035 |
-
|
| 5036 |
-
|
|
|
|
|
|
|
| 5037 |
|
| 5038 |
-
#===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5039 |
|
| 5040 |
enhanced_score_notes_with_patch_changes = []
|
| 5041 |
|
| 5042 |
patches = [-1] * 16
|
| 5043 |
|
|
|
|
|
|
|
|
|
|
| 5044 |
overflow_idx = -1
|
| 5045 |
|
| 5046 |
for idx, e in enumerate(enhanced_score_notes):
|
| 5047 |
-
|
| 5048 |
-
|
| 5049 |
-
|
| 5050 |
-
|
| 5051 |
-
|
| 5052 |
-
|
| 5053 |
-
|
| 5054 |
-
e[3] =
|
| 5055 |
-
|
| 5056 |
-
|
| 5057 |
-
|
| 5058 |
-
|
| 5059 |
-
|
| 5060 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5061 |
|
| 5062 |
-
|
| 5063 |
|
| 5064 |
#===========================================================================
|
| 5065 |
|
| 5066 |
overflow_patches = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5067 |
|
| 5068 |
if overflow_idx != -1:
|
| 5069 |
-
|
| 5070 |
-
|
| 5071 |
-
|
| 5072 |
-
|
| 5073 |
-
|
| 5074 |
-
|
| 5075 |
-
|
| 5076 |
-
|
| 5077 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5078 |
|
| 5079 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5080 |
|
| 5081 |
#===========================================================================
|
| 5082 |
|
|
@@ -5086,9 +5166,13 @@ def patch_enhanced_score_notes(enhanced_score_notes,
|
|
| 5086 |
|
| 5087 |
#===========================================================================
|
| 5088 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5089 |
if verbose:
|
| 5090 |
print('=' * 70)
|
| 5091 |
-
print('
|
| 5092 |
print('=' * 70)
|
| 5093 |
for c, p in enumerate(patches):
|
| 5094 |
print('Cha', str(c).zfill(2), '---', str(p).zfill(3), Number2patch[p])
|
|
@@ -5101,6 +5185,8 @@ def patch_enhanced_score_notes(enhanced_score_notes,
|
|
| 5101 |
print(str(p).zfill(3), Number2patch[p])
|
| 5102 |
print('=' * 70)
|
| 5103 |
|
|
|
|
|
|
|
| 5104 |
return enhanced_score_notes_with_patch_changes, patches, overflow_patches
|
| 5105 |
|
| 5106 |
###################################################################################
|
|
|
|
| 51 |
|
| 52 |
###################################################################################
|
| 53 |
|
| 54 |
+
__version__ = "25.7.8"
|
| 55 |
|
| 56 |
print('=' * 70)
|
| 57 |
print('TMIDIX Python module')
|
|
|
|
| 3724 |
chord.sort(key = lambda x: x[4], reverse=True)
|
| 3725 |
return chord
|
| 3726 |
|
| 3727 |
+
def adjust_score_velocities(score,
|
| 3728 |
+
max_velocity,
|
| 3729 |
+
adj_per_channel=False,
|
| 3730 |
+
adj_in_place=True
|
| 3731 |
+
):
|
| 3732 |
+
|
| 3733 |
+
if adj_in_place:
|
| 3734 |
+
buf = score
|
| 3735 |
+
|
| 3736 |
+
else:
|
| 3737 |
+
buf = copy.deepcopy(score)
|
| 3738 |
+
|
| 3739 |
+
notes = [evt for evt in buf if evt[0] == 'note']
|
| 3740 |
+
|
| 3741 |
+
if not notes:
|
| 3742 |
+
return buf
|
| 3743 |
|
| 3744 |
+
if adj_per_channel:
|
| 3745 |
+
channel_max = {}
|
| 3746 |
+
|
| 3747 |
+
for _, _, _, ch, _, vel, _ in notes:
|
| 3748 |
+
channel_max[ch] = max(channel_max.get(ch, 0), vel)
|
| 3749 |
+
|
| 3750 |
+
channel_factor = {
|
| 3751 |
+
ch: (max_velocity / vmax if vmax > 0 else 1.0)
|
| 3752 |
+
for ch, vmax in channel_max.items()
|
| 3753 |
+
}
|
| 3754 |
+
|
| 3755 |
+
for evt in buf:
|
| 3756 |
+
if evt[0] == 'note':
|
| 3757 |
+
ch = evt[3]
|
| 3758 |
+
factor = channel_factor.get(ch, 1.0)
|
| 3759 |
+
new_vel = int(evt[5] * factor)
|
| 3760 |
+
evt[5] = max(1, min(127, new_vel))
|
| 3761 |
|
|
|
|
|
|
|
|
|
|
| 3762 |
else:
|
| 3763 |
+
global_max = max(vel for _, _, _, _, _, vel, _ in notes)
|
| 3764 |
+
factor = max_velocity / global_max if global_max > 0 else 1.0
|
| 3765 |
+
|
| 3766 |
+
for evt in buf:
|
| 3767 |
+
if evt[0] == 'note':
|
| 3768 |
+
new_vel = int(evt[5] * factor)
|
| 3769 |
+
evt[5] = max(1, min(127, new_vel))
|
| 3770 |
+
|
| 3771 |
+
if not adj_in_place:
|
| 3772 |
+
return buf
|
| 3773 |
|
| 3774 |
def chordify_score(score,
|
| 3775 |
return_choridfied_score=True,
|
|
|
|
| 5062 |
|
| 5063 |
###################################################################################
|
| 5064 |
|
| 5065 |
+
def patch_enhanced_score_notes(escore_notes,
|
| 5066 |
+
default_patch=0,
|
| 5067 |
+
reserved_patch=-1,
|
| 5068 |
+
reserved_patch_channel=-1,
|
| 5069 |
+
drums_patch=9,
|
| 5070 |
+
verbose=False
|
| 5071 |
+
):
|
| 5072 |
|
| 5073 |
+
#===========================================================================
|
| 5074 |
+
|
| 5075 |
+
enhanced_score_notes = copy.deepcopy(escore_notes)
|
| 5076 |
+
|
| 5077 |
+
#===========================================================================
|
| 5078 |
|
| 5079 |
enhanced_score_notes_with_patch_changes = []
|
| 5080 |
|
| 5081 |
patches = [-1] * 16
|
| 5082 |
|
| 5083 |
+
if -1 < reserved_patch < 128 and -1 < reserved_patch_channel < 128:
|
| 5084 |
+
patches[reserved_patch_channel] = reserved_patch
|
| 5085 |
+
|
| 5086 |
overflow_idx = -1
|
| 5087 |
|
| 5088 |
for idx, e in enumerate(enhanced_score_notes):
|
| 5089 |
+
if e[0] == 'note':
|
| 5090 |
+
if e[3] != 9:
|
| 5091 |
+
if -1 < reserved_patch < 128 and -1 < reserved_patch_channel < 128:
|
| 5092 |
+
if e[6] == reserved_patch:
|
| 5093 |
+
e[3] = reserved_patch_channel
|
| 5094 |
+
|
| 5095 |
+
if patches[e[3]] == -1:
|
| 5096 |
+
patches[e[3]] = e[6]
|
| 5097 |
+
|
| 5098 |
+
else:
|
| 5099 |
+
if patches[e[3]] != e[6]:
|
| 5100 |
+
if e[6] in patches:
|
| 5101 |
+
e[3] = patches.index(e[6])
|
| 5102 |
+
|
| 5103 |
+
else:
|
| 5104 |
+
if -1 in patches:
|
| 5105 |
+
patches[patches.index(-1)] = e[6]
|
| 5106 |
+
|
| 5107 |
+
else:
|
| 5108 |
+
overflow_idx = idx
|
| 5109 |
+
break
|
| 5110 |
|
| 5111 |
+
enhanced_score_notes_with_patch_changes.append(e)
|
| 5112 |
|
| 5113 |
#===========================================================================
|
| 5114 |
|
| 5115 |
overflow_patches = []
|
| 5116 |
+
overflow_channels = [-1] * 16
|
| 5117 |
+
overflow_channels[9] = drums_patch
|
| 5118 |
+
|
| 5119 |
+
if -1 < reserved_patch < 128 and -1 < reserved_patch_channel < 128:
|
| 5120 |
+
overflow_channels[reserved_patch_channel] = reserved_patch
|
| 5121 |
|
| 5122 |
if overflow_idx != -1:
|
| 5123 |
+
for idx, e in enumerate(enhanced_score_notes[overflow_idx:]):
|
| 5124 |
+
if e[0] == 'note':
|
| 5125 |
+
if e[3] != 9:
|
| 5126 |
+
if e[6] not in overflow_channels:
|
| 5127 |
+
|
| 5128 |
+
if -1 in overflow_channels:
|
| 5129 |
+
free_chan = overflow_channels.index(-1)
|
| 5130 |
+
overflow_channels[free_chan] = e[6]
|
| 5131 |
+
e[3] = free_chan
|
| 5132 |
+
|
| 5133 |
+
enhanced_score_notes_with_patch_changes.append(['patch_change', e[1], e[3], e[6]])
|
| 5134 |
+
|
| 5135 |
+
overflow_patches.append(e[6])
|
| 5136 |
+
|
| 5137 |
+
else:
|
| 5138 |
+
overflow_channels = [-1] * 16
|
| 5139 |
+
overflow_channels[9] = drums_patch
|
| 5140 |
+
|
| 5141 |
+
if -1 < reserved_patch < 128 and -1 < reserved_patch_channel < 128:
|
| 5142 |
+
overflow_channels[reserved_patch_channel] = reserved_patch
|
| 5143 |
+
e[3] = reserved_patch_channel
|
| 5144 |
|
| 5145 |
+
if e[6] != reserved_patch:
|
| 5146 |
+
|
| 5147 |
+
free_chan = overflow_channels.index(-1)
|
| 5148 |
+
e[3] = free_chan
|
| 5149 |
+
|
| 5150 |
+
overflow_channels[e[3]] = e[6]
|
| 5151 |
+
|
| 5152 |
+
enhanced_score_notes_with_patch_changes.append(['patch_change', e[1], e[3], e[6]])
|
| 5153 |
+
|
| 5154 |
+
overflow_patches.append(e[6])
|
| 5155 |
+
|
| 5156 |
+
else:
|
| 5157 |
+
e[3] = overflow_channels.index(e[6])
|
| 5158 |
+
|
| 5159 |
+
enhanced_score_notes_with_patch_changes.append(e)
|
| 5160 |
|
| 5161 |
#===========================================================================
|
| 5162 |
|
|
|
|
| 5166 |
|
| 5167 |
#===========================================================================
|
| 5168 |
|
| 5169 |
+
overflow_patches = ordered_set(overflow_patches)
|
| 5170 |
+
|
| 5171 |
+
#===========================================================================
|
| 5172 |
+
|
| 5173 |
if verbose:
|
| 5174 |
print('=' * 70)
|
| 5175 |
+
print('Main composition patches')
|
| 5176 |
print('=' * 70)
|
| 5177 |
for c, p in enumerate(patches):
|
| 5178 |
print('Cha', str(c).zfill(2), '---', str(p).zfill(3), Number2patch[p])
|
|
|
|
| 5185 |
print(str(p).zfill(3), Number2patch[p])
|
| 5186 |
print('=' * 70)
|
| 5187 |
|
| 5188 |
+
#===========================================================================
|
| 5189 |
+
|
| 5190 |
return enhanced_score_notes_with_patch_changes, patches, overflow_patches
|
| 5191 |
|
| 5192 |
###################################################################################
|
midi_to_colab_audio.py
CHANGED
|
@@ -5,14 +5,14 @@ r'''#===========================================================================
|
|
| 5 |
# Converts any MIDI file to raw audio which is compatible
|
| 6 |
# with Google Colab or HUgging Face Gradio
|
| 7 |
#
|
| 8 |
-
# Version
|
| 9 |
#
|
| 10 |
-
# Includes full source code of MIDI
|
| 11 |
#
|
| 12 |
-
# Original source code for all modules was retrieved on
|
| 13 |
#
|
| 14 |
# Project Los Angeles
|
| 15 |
-
# Tegridy Code
|
| 16 |
#
|
| 17 |
#===================================================================================================================
|
| 18 |
#
|
|
@@ -1773,7 +1773,7 @@ def _encode(events_lol, unknown_callback=None, never_add_eot=False,
|
|
| 1773 |
|
| 1774 |
Python bindings for FluidSynth
|
| 1775 |
|
| 1776 |
-
Copyright 2008, Nathan Whitehead <nwhitehe@gmail.com>
|
| 1777 |
|
| 1778 |
|
| 1779 |
Released under the LGPL
|
|
@@ -1790,27 +1790,67 @@ def _encode(events_lol, unknown_callback=None, never_add_eot=False,
|
|
| 1790 |
================================================================================
|
| 1791 |
"""
|
| 1792 |
|
| 1793 |
-
from ctypes import *
|
| 1794 |
-
from ctypes.util import find_library
|
| 1795 |
import os
|
| 1796 |
-
|
| 1797 |
-
|
| 1798 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1799 |
|
| 1800 |
# DLL search method changed in Python 3.8
|
| 1801 |
# https://docs.python.org/3/library/os.html#os.add_dll_directory
|
| 1802 |
-
if hasattr(os, 'add_dll_directory'):
|
| 1803 |
os.add_dll_directory(os.getcwd())
|
|
|
|
|
|
|
|
|
|
| 1804 |
|
| 1805 |
-
|
| 1806 |
-
|
| 1807 |
-
|
| 1808 |
-
|
| 1809 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1810 |
|
| 1811 |
-
if lib is None:
|
| 1812 |
raise ImportError("Couldn't find the FluidSynth library.")
|
| 1813 |
|
|
|
|
|
|
|
| 1814 |
# Dynamically link the FluidSynth library
|
| 1815 |
# Architecture (32-/64-bit) must match your Python version
|
| 1816 |
_fl = CDLL(lib)
|
|
@@ -1829,7 +1869,7 @@ def cfunc(name, result, *args):
|
|
| 1829 |
return None
|
| 1830 |
|
| 1831 |
# Bump this up when changing the interface for users
|
| 1832 |
-
api_version = '1.3.
|
| 1833 |
|
| 1834 |
# Function prototypes for C versions of functions
|
| 1835 |
|
|
@@ -1843,10 +1883,7 @@ fluid_version = cfunc('fluid_version', c_void_p,
|
|
| 1843 |
|
| 1844 |
majver = c_int()
|
| 1845 |
fluid_version(majver, c_int(), c_int())
|
| 1846 |
-
if majver.value > 1
|
| 1847 |
-
FLUIDSETTING_EXISTS = FLUID_OK
|
| 1848 |
-
else:
|
| 1849 |
-
FLUIDSETTING_EXISTS = 1
|
| 1850 |
|
| 1851 |
# fluid settings
|
| 1852 |
new_fluid_settings = cfunc('new_fluid_settings', c_void_p)
|
|
@@ -2086,9 +2123,18 @@ fluid_synth_set_chorus_level = cfunc('fluid_synth_set_chorus_level', c_int,
|
|
| 2086 |
('synth', c_void_p, 1),
|
| 2087 |
('level', c_double, 1))
|
| 2088 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2089 |
fluid_synth_set_chorus_type = cfunc('fluid_synth_set_chorus_type', c_int,
|
| 2090 |
('synth', c_void_p, 1),
|
| 2091 |
('type', c_int, 1))
|
|
|
|
| 2092 |
fluid_synth_get_reverb_roomsize = cfunc('fluid_synth_get_reverb_roomsize', c_double,
|
| 2093 |
('synth', c_void_p, 1))
|
| 2094 |
|
|
@@ -2220,6 +2266,77 @@ fluid_midi_event_get_value = cfunc('fluid_midi_event_get_value', c_int,
|
|
| 2220 |
fluid_midi_event_get_velocity = cfunc('fluid_midi_event_get_velocity', c_int,
|
| 2221 |
('evt', c_void_p, 1))
|
| 2222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2223 |
# fluid_player_status returned by fluid_player_get_status()
|
| 2224 |
FLUID_PLAYER_READY = 0
|
| 2225 |
FLUID_PLAYER_PLAYING = 1
|
|
@@ -2281,6 +2398,9 @@ new_fluid_midi_driver = cfunc('new_fluid_midi_driver', c_void_p,
|
|
| 2281 |
('handler', CFUNCTYPE(c_int, c_void_p, c_void_p), 1),
|
| 2282 |
('event_handler_data', c_void_p, 1))
|
| 2283 |
|
|
|
|
|
|
|
|
|
|
| 2284 |
|
| 2285 |
# fluid midi router rule
|
| 2286 |
class fluid_midi_router_t(Structure):
|
|
@@ -2342,6 +2462,16 @@ fluid_midi_router_add_rule = cfunc('fluid_midi_router_add_rule', c_int,
|
|
| 2342 |
('rule', c_void_p, 1),
|
| 2343 |
('type', c_int, 1))
|
| 2344 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2345 |
# fluidsynth 2.x
|
| 2346 |
new_fluid_cmd_handler=cfunc('new_fluid_cmd_handler', c_void_p,
|
| 2347 |
('synth', c_void_p, 1),
|
|
@@ -2416,6 +2546,7 @@ class Synth:
|
|
| 2416 |
self.audio_driver = None
|
| 2417 |
self.midi_driver = None
|
| 2418 |
self.router = None
|
|
|
|
| 2419 |
def setting(self, opt, val):
|
| 2420 |
"""change an arbitrary synth setting, type-smart"""
|
| 2421 |
if isinstance(val, (str, bytes)):
|
|
@@ -2451,11 +2582,11 @@ class Synth:
|
|
| 2451 |
see http://www.fluidsynth.org/api/fluidsettings.xml for allowed values and defaults by platform
|
| 2452 |
"""
|
| 2453 |
driver = driver or self.get_setting('audio.driver')
|
| 2454 |
-
device = device or self.get_setting('audio
|
| 2455 |
midi_driver = midi_driver or self.get_setting('midi.driver')
|
| 2456 |
|
| 2457 |
self.setting('audio.driver', driver)
|
| 2458 |
-
self.setting('audio
|
| 2459 |
self.audio_driver = new_fluid_audio_driver(self.settings, self.synth)
|
| 2460 |
self.setting('midi.driver', midi_driver)
|
| 2461 |
self.router = new_fluid_midi_router(self.settings, fluid_synth_handle_midi_event, self.synth)
|
|
@@ -2463,7 +2594,7 @@ class Synth:
|
|
| 2463 |
new_fluid_cmd_handler(self.synth, self.router)
|
| 2464 |
else:
|
| 2465 |
fluid_synth_set_midi_router(self.synth, self.router)
|
| 2466 |
-
if midi_router
|
| 2467 |
self.midi_driver = new_fluid_midi_driver(self.settings, fluid_midi_router_handle_midi_event, self.router)
|
| 2468 |
self.custom_router_callback = None
|
| 2469 |
else: ## Supply an external MIDI event handler
|
|
@@ -2474,6 +2605,8 @@ class Synth:
|
|
| 2474 |
def delete(self):
|
| 2475 |
if self.audio_driver:
|
| 2476 |
delete_fluid_audio_driver(self.audio_driver)
|
|
|
|
|
|
|
| 2477 |
delete_fluid_synth(self.synth)
|
| 2478 |
delete_fluid_settings(self.settings)
|
| 2479 |
def sfload(self, filename, update_midi_preset=0):
|
|
@@ -2518,8 +2651,7 @@ class Synth:
|
|
| 2518 |
return None
|
| 2519 |
return fluid_preset_get_name(preset).decode('ascii')
|
| 2520 |
else:
|
| 2521 |
-
|
| 2522 |
-
return presetname
|
| 2523 |
def router_clear(self):
|
| 2524 |
if self.router is not None:
|
| 2525 |
fluid_midi_router_clear_rules(self.router)
|
|
@@ -2570,16 +2702,16 @@ class Synth:
|
|
| 2570 |
if fluid_synth_set_reverb is not None:
|
| 2571 |
return fluid_synth_set_reverb(self.synth, roomsize, damping, width, level)
|
| 2572 |
else:
|
| 2573 |
-
|
| 2574 |
if roomsize>=0:
|
| 2575 |
-
|
| 2576 |
if damping>=0:
|
| 2577 |
-
|
| 2578 |
if width>=0:
|
| 2579 |
-
|
| 2580 |
if level>=0:
|
| 2581 |
-
|
| 2582 |
-
return fluid_synth_set_reverb_full(self.synth,
|
| 2583 |
def set_chorus(self, nr=-1, level=-1.0, speed=-1.0, depth=-1.0, type=-1):
|
| 2584 |
"""
|
| 2585 |
nr Chorus voice count (0-99, CPU time consumption proportional to this value)
|
|
@@ -2632,17 +2764,17 @@ class Synth:
|
|
| 2632 |
if fluid_synth_set_chorus_level is not None:
|
| 2633 |
return fluid_synth_set_chorus_level(self.synth, level)
|
| 2634 |
else:
|
| 2635 |
-
return self.set_chorus(
|
| 2636 |
def set_chorus_speed(self, speed):
|
| 2637 |
if fluid_synth_set_chorus_speed is not None:
|
| 2638 |
return fluid_synth_set_chorus_speed(self.synth, speed)
|
| 2639 |
else:
|
| 2640 |
return self.set_chorus(speed=speed)
|
| 2641 |
-
def set_chorus_depth(self,
|
| 2642 |
if fluid_synth_set_chorus_depth is not None:
|
| 2643 |
-
return fluid_synth_set_chorus_depth(self.synth,
|
| 2644 |
else:
|
| 2645 |
-
return self.set_chorus(depth=
|
| 2646 |
def set_chorus_type(self, type):
|
| 2647 |
if fluid_synth_set_chorus_type is not None:
|
| 2648 |
return fluid_synth_set_chorus_type(self.synth, type)
|
|
@@ -2694,10 +2826,10 @@ class Synth:
|
|
| 2694 |
A pitch bend value of 0 is no pitch change from default.
|
| 2695 |
A value of -2048 is 1 semitone down.
|
| 2696 |
A value of 2048 is 1 semitone up.
|
| 2697 |
-
Maximum values are -8192 to +
|
| 2698 |
|
| 2699 |
"""
|
| 2700 |
-
return fluid_synth_pitch_bend(self.synth, chan, val + 8192)
|
| 2701 |
def cc(self, chan, ctrl, val):
|
| 2702 |
"""Send control change value
|
| 2703 |
|
|
@@ -2747,8 +2879,15 @@ class Synth:
|
|
| 2747 |
|
| 2748 |
"""
|
| 2749 |
return fluid_synth_write_s16_stereo(self.synth, len)
|
| 2750 |
-
def tuning_dump(self, bank, prog
|
| 2751 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2752 |
|
| 2753 |
def midi_event_get_type(self, event):
|
| 2754 |
return fluid_midi_event_get_type(event)
|
|
@@ -2767,17 +2906,20 @@ class Synth:
|
|
| 2767 |
|
| 2768 |
def play_midi_file(self, filename):
|
| 2769 |
self.player = new_fluid_player(self.synth)
|
| 2770 |
-
if self.player
|
| 2771 |
-
|
|
|
|
| 2772 |
fluid_player_set_playback_callback(self.player, self.custom_router_callback, self.synth)
|
| 2773 |
status = fluid_player_add(self.player, filename.encode())
|
| 2774 |
-
if status == FLUID_FAILED:
|
|
|
|
| 2775 |
status = fluid_player_play(self.player)
|
| 2776 |
return status
|
| 2777 |
|
| 2778 |
def play_midi_stop(self):
|
| 2779 |
status = fluid_player_stop(self.player)
|
| 2780 |
-
if status == FLUID_FAILED:
|
|
|
|
| 2781 |
status = fluid_player_seek(self.player, 0)
|
| 2782 |
delete_fluid_player(self.player)
|
| 2783 |
return status
|
|
@@ -2785,7 +2927,151 @@ class Synth:
|
|
| 2785 |
def player_set_tempo(self, tempo_type, tempo):
|
| 2786 |
return fluid_player_set_tempo(self.player, tempo_type, tempo)
|
| 2787 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2788 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2789 |
|
| 2790 |
class Sequencer:
|
| 2791 |
def __init__(self, time_scale=1000, use_system_timer=True):
|
|
@@ -2802,14 +3088,14 @@ class Sequencer:
|
|
| 2802 |
def register_fluidsynth(self, synth):
|
| 2803 |
response = fluid_sequencer_register_fluidsynth(self.sequencer, synth.synth)
|
| 2804 |
if response == FLUID_FAILED:
|
| 2805 |
-
raise
|
| 2806 |
return response
|
| 2807 |
|
| 2808 |
def register_client(self, name, callback, data=None):
|
| 2809 |
c_callback = CFUNCTYPE(None, c_uint, c_void_p, c_void_p, c_void_p)(callback)
|
| 2810 |
response = fluid_sequencer_register_client(self.sequencer, name.encode(), c_callback, data)
|
| 2811 |
if response == FLUID_FAILED:
|
| 2812 |
-
raise
|
| 2813 |
|
| 2814 |
# store in a list to prevent garbage collection
|
| 2815 |
self.client_callbacks.append(c_callback)
|
|
@@ -2849,7 +3135,7 @@ class Sequencer:
|
|
| 2849 |
def _schedule_event(self, evt, time, absolute=True):
|
| 2850 |
response = fluid_sequencer_send_at(self.sequencer, evt, time, absolute)
|
| 2851 |
if response == FLUID_FAILED:
|
| 2852 |
-
raise
|
| 2853 |
|
| 2854 |
def get_tick(self):
|
| 2855 |
return fluid_sequencer_get_tick(self.sequencer)
|
|
@@ -2868,123 +3154,248 @@ def raw_audio_string(data):
|
|
| 2868 |
|
| 2869 |
"""
|
| 2870 |
import numpy
|
| 2871 |
-
return (data.astype(numpy.int16)).
|
| 2872 |
|
| 2873 |
#===============================================================================
|
| 2874 |
|
| 2875 |
import numpy as np
|
| 2876 |
import wave
|
| 2877 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2878 |
def midi_opus_to_colab_audio(midi_opus,
|
| 2879 |
soundfont_path='/usr/share/sounds/sf2/FluidR3_GM.sf2',
|
| 2880 |
sample_rate=16000, # 44100
|
| 2881 |
-
|
| 2882 |
trim_silence=True,
|
| 2883 |
silence_threshold=0.1,
|
| 2884 |
output_for_gradio=False,
|
| 2885 |
write_audio_to_WAV=''
|
| 2886 |
):
|
| 2887 |
|
| 2888 |
-
def normalize_volume(matrix, factor=10):
|
| 2889 |
-
norm = np.linalg.norm(matrix)
|
| 2890 |
-
matrix = matrix/norm # normalized matrix
|
| 2891 |
-
mult_matrix = matrix * factor
|
| 2892 |
-
final_matrix = np.clip(mult_matrix, -1.0, 1.0)
|
| 2893 |
-
return final_matrix
|
| 2894 |
-
|
| 2895 |
if midi_opus[1]:
|
| 2896 |
|
| 2897 |
-
|
| 2898 |
-
|
| 2899 |
-
|
| 2900 |
-
|
| 2901 |
-
|
| 2902 |
-
|
| 2903 |
-
|
| 2904 |
-
|
| 2905 |
-
|
| 2906 |
-
|
| 2907 |
-
|
| 2908 |
-
|
| 2909 |
-
|
| 2910 |
-
|
| 2911 |
-
|
| 2912 |
-
|
| 2913 |
-
|
| 2914 |
-
|
| 2915 |
-
|
| 2916 |
-
|
| 2917 |
-
|
| 2918 |
-
|
| 2919 |
-
|
| 2920 |
-
|
| 2921 |
-
|
| 2922 |
-
|
| 2923 |
-
|
| 2924 |
-
|
| 2925 |
-
|
| 2926 |
-
|
| 2927 |
-
|
| 2928 |
-
|
| 2929 |
-
|
| 2930 |
-
|
| 2931 |
-
|
| 2932 |
-
|
| 2933 |
-
|
| 2934 |
-
|
| 2935 |
-
|
| 2936 |
-
|
| 2937 |
-
|
| 2938 |
-
|
| 2939 |
-
|
| 2940 |
-
|
| 2941 |
-
|
| 2942 |
-
|
| 2943 |
-
|
| 2944 |
-
|
| 2945 |
-
|
| 2946 |
-
|
| 2947 |
-
|
| 2948 |
-
|
| 2949 |
-
|
| 2950 |
-
|
| 2951 |
-
|
| 2952 |
-
|
| 2953 |
-
|
| 2954 |
-
|
| 2955 |
-
|
| 2956 |
-
|
| 2957 |
-
|
| 2958 |
-
|
| 2959 |
-
|
| 2960 |
-
|
| 2961 |
-
|
| 2962 |
-
|
| 2963 |
-
|
| 2964 |
-
|
| 2965 |
-
|
| 2966 |
-
|
| 2967 |
-
|
| 2968 |
-
|
| 2969 |
-
|
| 2970 |
-
|
| 2971 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2972 |
|
| 2973 |
else:
|
| 2974 |
return None
|
| 2975 |
|
| 2976 |
-
|
| 2977 |
-
|
| 2978 |
-
|
| 2979 |
-
|
|
|
|
|
|
|
| 2980 |
trim_silence=True,
|
| 2981 |
silence_threshold=0.1,
|
| 2982 |
output_for_gradio=False,
|
| 2983 |
write_audio_to_WAV=False
|
| 2984 |
-
|
| 2985 |
-
|
| 2986 |
-
'''
|
| 2987 |
-
|
| 2988 |
Returns raw audio to pass to IPython.disaply.Audio func
|
| 2989 |
|
| 2990 |
Example usage:
|
|
@@ -2992,99 +3403,144 @@ def midi_to_colab_audio(midi_file,
|
|
| 2992 |
from IPython.display import Audio
|
| 2993 |
|
| 2994 |
display(Audio(raw_audio, rate=16000, normalize=False))
|
| 2995 |
-
|
| 2996 |
-
'''
|
| 2997 |
-
|
| 2998 |
-
def normalize_volume(matrix, factor=10):
|
| 2999 |
-
norm = np.linalg.norm(matrix)
|
| 3000 |
-
matrix = matrix/norm # normalized matrix
|
| 3001 |
-
mult_matrix = matrix * factor
|
| 3002 |
-
final_matrix = np.clip(mult_matrix, -1.0, 1.0)
|
| 3003 |
-
return final_matrix
|
| 3004 |
-
|
| 3005 |
-
midi_opus = midi2opus(open(midi_file, 'rb').read())
|
| 3006 |
|
| 3007 |
-
|
|
|
|
|
|
|
|
|
|
| 3008 |
|
| 3009 |
-
|
| 3010 |
-
|
| 3011 |
-
|
| 3012 |
-
|
| 3013 |
-
|
| 3014 |
-
|
| 3015 |
-
|
| 3016 |
-
|
| 3017 |
-
|
| 3018 |
-
|
| 3019 |
-
|
| 3020 |
-
|
| 3021 |
-
|
| 3022 |
-
|
| 3023 |
-
|
| 3024 |
-
|
| 3025 |
-
|
| 3026 |
-
|
| 3027 |
-
|
| 3028 |
-
|
| 3029 |
-
|
| 3030 |
-
|
| 3031 |
-
|
| 3032 |
-
|
| 3033 |
-
|
| 3034 |
-
|
| 3035 |
-
|
| 3036 |
-
|
| 3037 |
-
|
| 3038 |
-
|
| 3039 |
-
|
| 3040 |
-
|
| 3041 |
-
|
| 3042 |
-
|
| 3043 |
-
|
| 3044 |
-
|
| 3045 |
-
|
| 3046 |
-
|
| 3047 |
-
|
| 3048 |
-
|
| 3049 |
-
|
| 3050 |
-
|
| 3051 |
-
|
| 3052 |
-
|
| 3053 |
-
|
| 3054 |
-
|
| 3055 |
-
|
| 3056 |
-
|
| 3057 |
-
|
| 3058 |
-
|
| 3059 |
-
|
| 3060 |
-
|
| 3061 |
-
|
| 3062 |
-
|
| 3063 |
-
|
| 3064 |
-
|
| 3065 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3066 |
|
| 3067 |
-
|
|
|
|
|
|
|
| 3068 |
|
| 3069 |
-
|
|
|
|
| 3070 |
|
| 3071 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3072 |
|
| 3073 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3074 |
|
| 3075 |
-
|
|
|
|
|
|
|
| 3076 |
|
| 3077 |
-
|
|
|
|
|
|
|
| 3078 |
|
| 3079 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3080 |
wf.setframerate(sample_rate)
|
| 3081 |
wf.setsampwidth(2)
|
| 3082 |
-
wf.setnchannels(
|
| 3083 |
-
wf.writeframes(
|
|
|
|
|
|
|
| 3084 |
|
| 3085 |
-
return raw_audio
|
| 3086 |
-
|
| 3087 |
-
else:
|
| 3088 |
-
return None
|
| 3089 |
-
|
| 3090 |
#===================================================================================================================
|
|
|
|
| 5 |
# Converts any MIDI file to raw audio which is compatible
|
| 6 |
# with Google Colab or HUgging Face Gradio
|
| 7 |
#
|
| 8 |
+
# Version 2.0
|
| 9 |
#
|
| 10 |
+
# Includes full source code of MIDI and pyfluidsynth
|
| 11 |
#
|
| 12 |
+
# Original source code for all modules was retrieved on 07/31/2025
|
| 13 |
#
|
| 14 |
# Project Los Angeles
|
| 15 |
+
# Tegridy Code 2025
|
| 16 |
#
|
| 17 |
#===================================================================================================================
|
| 18 |
#
|
|
|
|
| 1773 |
|
| 1774 |
Python bindings for FluidSynth
|
| 1775 |
|
| 1776 |
+
Copyright 2008--2024, Nathan Whitehead <nwhitehe@gmail.com> and others.
|
| 1777 |
|
| 1778 |
|
| 1779 |
Released under the LGPL
|
|
|
|
| 1790 |
================================================================================
|
| 1791 |
"""
|
| 1792 |
|
|
|
|
|
|
|
| 1793 |
import os
|
| 1794 |
+
from ctypes import (
|
| 1795 |
+
CDLL,
|
| 1796 |
+
CFUNCTYPE,
|
| 1797 |
+
POINTER,
|
| 1798 |
+
Structure,
|
| 1799 |
+
byref,
|
| 1800 |
+
c_char,
|
| 1801 |
+
c_char_p,
|
| 1802 |
+
c_double,
|
| 1803 |
+
c_float,
|
| 1804 |
+
c_int,
|
| 1805 |
+
c_short,
|
| 1806 |
+
c_uint,
|
| 1807 |
+
c_void_p,
|
| 1808 |
+
create_string_buffer,
|
| 1809 |
+
)
|
| 1810 |
+
from ctypes.util import find_library
|
| 1811 |
|
| 1812 |
# DLL search method changed in Python 3.8
|
| 1813 |
# https://docs.python.org/3/library/os.html#os.add_dll_directory
|
| 1814 |
+
if hasattr(os, 'add_dll_directory'): # Python 3.8+ on Windows only
|
| 1815 |
os.add_dll_directory(os.getcwd())
|
| 1816 |
+
os.add_dll_directory('C:\\tools\\fluidsynth\\bin')
|
| 1817 |
+
# Workaround bug in find_library, it doesn't recognize add_dll_directory
|
| 1818 |
+
os.environ['PATH'] += ';C:\\tools\\fluidsynth\\bin'
|
| 1819 |
|
| 1820 |
+
# A function to find the FluidSynth library
|
| 1821 |
+
# (mostly needed for Windows distributions of libfluidsynth supplied with QSynth)
|
| 1822 |
+
def find_libfluidsynth(debug_print: bool = False) -> str:
|
| 1823 |
+
r"""
|
| 1824 |
+
macOS X64:
|
| 1825 |
+
* 'fluidsynth' was found at /usr/local/opt/fluid-synth/lib/libfluidsynth.dylib.
|
| 1826 |
+
macOS ARM64:
|
| 1827 |
+
* 'fluidsynth' was found at /opt/homebrew/opt/fluid-synth/lib/libfluidsynth.dylib.
|
| 1828 |
+
Ubuntu X86:
|
| 1829 |
+
* 'fluidsynth' was found at libfluidsynth.so.3.
|
| 1830 |
+
Windows X86:
|
| 1831 |
+
* 'libfluidsynth-3' was found at C:\tools\fluidsynth\bin\libfluidsynth-3.dll. --or--
|
| 1832 |
+
* 'fluidsynth-3' was found as C:\tools\fluidsynth\bin\fluidsynth-3.dll. >= v2.4.5
|
| 1833 |
+
* https://github.com/FluidSynth/fluidsynth/issues/1543
|
| 1834 |
+
"""
|
| 1835 |
+
libs = "fluidsynth fluidsynth-3 libfluidsynth libfluidsynth-3 libfluidsynth-2 libfluidsynth-1"
|
| 1836 |
+
for lib_name in libs.split():
|
| 1837 |
+
lib = find_library(lib_name)
|
| 1838 |
+
if lib:
|
| 1839 |
+
if debug_print:
|
| 1840 |
+
print(f"'{lib_name}' was found at {lib}.")
|
| 1841 |
+
return lib
|
| 1842 |
+
|
| 1843 |
+
# On macOS on Apple silicon, non-Homebrew Python distributions fail to locate
|
| 1844 |
+
# homebrew-installed instances of FluidSynth. This workaround addresses this.
|
| 1845 |
+
if homebrew_prefix := os.getenv("HOMEBREW_PREFIX"):
|
| 1846 |
+
lib = os.path.join(homebrew_prefix, "lib", "libfluidsynth.dylib")
|
| 1847 |
+
if os.path.exists(lib):
|
| 1848 |
+
return lib
|
| 1849 |
|
|
|
|
| 1850 |
raise ImportError("Couldn't find the FluidSynth library.")
|
| 1851 |
|
| 1852 |
+
lib = find_libfluidsynth()
|
| 1853 |
+
|
| 1854 |
# Dynamically link the FluidSynth library
|
| 1855 |
# Architecture (32-/64-bit) must match your Python version
|
| 1856 |
_fl = CDLL(lib)
|
|
|
|
| 1869 |
return None
|
| 1870 |
|
| 1871 |
# Bump this up when changing the interface for users
|
| 1872 |
+
api_version = '1.3.5'
|
| 1873 |
|
| 1874 |
# Function prototypes for C versions of functions
|
| 1875 |
|
|
|
|
| 1883 |
|
| 1884 |
majver = c_int()
|
| 1885 |
fluid_version(majver, c_int(), c_int())
|
| 1886 |
+
FLUIDSETTING_EXISTS = FLUID_OK if majver.value > 1 else 1
|
|
|
|
|
|
|
|
|
|
| 1887 |
|
| 1888 |
# fluid settings
|
| 1889 |
new_fluid_settings = cfunc('new_fluid_settings', c_void_p)
|
|
|
|
| 2123 |
('synth', c_void_p, 1),
|
| 2124 |
('level', c_double, 1))
|
| 2125 |
|
| 2126 |
+
fluid_synth_set_chorus_speed = cfunc('fluid_synth_set_chorus_speed', c_int,
|
| 2127 |
+
('synth', c_void_p, 1),
|
| 2128 |
+
('speed', c_double, 1))
|
| 2129 |
+
|
| 2130 |
+
fluid_synth_set_chorus_depth = cfunc('fluid_synth_set_chorus_depth', c_int,
|
| 2131 |
+
('synth', c_void_p, 1),
|
| 2132 |
+
('depth_ms', c_double, 1))
|
| 2133 |
+
|
| 2134 |
fluid_synth_set_chorus_type = cfunc('fluid_synth_set_chorus_type', c_int,
|
| 2135 |
('synth', c_void_p, 1),
|
| 2136 |
('type', c_int, 1))
|
| 2137 |
+
|
| 2138 |
fluid_synth_get_reverb_roomsize = cfunc('fluid_synth_get_reverb_roomsize', c_double,
|
| 2139 |
('synth', c_void_p, 1))
|
| 2140 |
|
|
|
|
| 2266 |
fluid_midi_event_get_velocity = cfunc('fluid_midi_event_get_velocity', c_int,
|
| 2267 |
('evt', c_void_p, 1))
|
| 2268 |
|
| 2269 |
+
# fluid modulator
|
| 2270 |
+
new_fluid_mod = cfunc("new_fluid_mod", c_void_p)
|
| 2271 |
+
|
| 2272 |
+
delete_fluid_mod = cfunc("delete_fluid_mod", c_void_p, ("mod", c_void_p, 1))
|
| 2273 |
+
|
| 2274 |
+
fluid_mod_clone = cfunc(
|
| 2275 |
+
"fluid_mod_clone", c_void_p, ("mod", c_void_p, 1), ("src", c_void_p, 1),
|
| 2276 |
+
)
|
| 2277 |
+
|
| 2278 |
+
fluid_mod_get_amount = cfunc("fluid_mod_get_amount", c_void_p, ("mod", c_void_p, 1))
|
| 2279 |
+
|
| 2280 |
+
fluid_mod_get_dest = cfunc("fluid_mod_get_dest", c_void_p, ("mod", c_void_p, 1))
|
| 2281 |
+
|
| 2282 |
+
fluid_mod_get_flags1 = cfunc("fluid_mod_get_flags1", c_void_p, ("mod", c_void_p, 1))
|
| 2283 |
+
|
| 2284 |
+
fluid_mod_get_flags2 = cfunc("fluid_mod_get_flags2", c_void_p, ("mod", c_void_p, 1))
|
| 2285 |
+
|
| 2286 |
+
fluid_mod_get_source1 = cfunc("fluid_mod_get_source1", c_void_p, ("mod", c_void_p, 1))
|
| 2287 |
+
|
| 2288 |
+
fluid_mod_get_source2 = cfunc("fluid_mod_get_source2", c_void_p, ("mod", c_void_p, 1))
|
| 2289 |
+
|
| 2290 |
+
fluid_mod_get_transform = cfunc(
|
| 2291 |
+
"fluid_mod_get_transform", c_void_p, ("mod", c_void_p, 1),
|
| 2292 |
+
)
|
| 2293 |
+
|
| 2294 |
+
fluid_mod_has_dest = cfunc(
|
| 2295 |
+
"fluid_mod_has_dest", c_void_p, ("mod", c_void_p, 1), ("gen", c_uint, 1),
|
| 2296 |
+
)
|
| 2297 |
+
|
| 2298 |
+
fluid_mod_has_source = cfunc(
|
| 2299 |
+
"fluid_mod_has_dest",
|
| 2300 |
+
c_void_p,
|
| 2301 |
+
("mod", c_void_p, 1),
|
| 2302 |
+
("cc", c_uint, 1),
|
| 2303 |
+
("ctrl", c_uint, 1),
|
| 2304 |
+
)
|
| 2305 |
+
|
| 2306 |
+
fluid_mod_set_amount = cfunc(
|
| 2307 |
+
"fluid_mod_set_amount", c_void_p, ("mod", c_void_p, 1), ("amount", c_double, 1),
|
| 2308 |
+
)
|
| 2309 |
+
|
| 2310 |
+
fluid_mod_set_dest = cfunc(
|
| 2311 |
+
"fluid_mod_set_dest", c_void_p, ("mod", c_void_p, 1), ("dst", c_int, 1),
|
| 2312 |
+
)
|
| 2313 |
+
|
| 2314 |
+
fluid_mod_set_source1 = cfunc(
|
| 2315 |
+
"fluid_mod_set_source1",
|
| 2316 |
+
c_void_p,
|
| 2317 |
+
("mod", c_void_p, 1),
|
| 2318 |
+
("src", c_int, 1),
|
| 2319 |
+
("flags", c_int, 1),
|
| 2320 |
+
)
|
| 2321 |
+
|
| 2322 |
+
fluid_mod_set_source2 = cfunc(
|
| 2323 |
+
"fluid_mod_set_source2",
|
| 2324 |
+
c_void_p,
|
| 2325 |
+
("mod", c_void_p, 1),
|
| 2326 |
+
("src", c_int, 1),
|
| 2327 |
+
("flags", c_int, 1),
|
| 2328 |
+
)
|
| 2329 |
+
|
| 2330 |
+
fluid_mod_set_transform = cfunc(
|
| 2331 |
+
"fluid_mod_set_transform", c_void_p, ("mod", c_void_p, 1), ("type", c_int, 1),
|
| 2332 |
+
)
|
| 2333 |
+
|
| 2334 |
+
fluid_mod_sizeof = cfunc("fluid_mod_sizeof", c_void_p)
|
| 2335 |
+
|
| 2336 |
+
fluid_mod_test_identity = cfunc(
|
| 2337 |
+
"fluid_mod_test_identity", c_void_p, ("mod1", c_void_p, 1), ("mod2", c_void_p, 1),
|
| 2338 |
+
)
|
| 2339 |
+
|
| 2340 |
# fluid_player_status returned by fluid_player_get_status()
|
| 2341 |
FLUID_PLAYER_READY = 0
|
| 2342 |
FLUID_PLAYER_PLAYING = 1
|
|
|
|
| 2398 |
('handler', CFUNCTYPE(c_int, c_void_p, c_void_p), 1),
|
| 2399 |
('event_handler_data', c_void_p, 1))
|
| 2400 |
|
| 2401 |
+
delete_fluid_midi_driver = cfunc('delete_fluid_midi_driver', None,
|
| 2402 |
+
('driver', c_void_p, 1))
|
| 2403 |
+
|
| 2404 |
|
| 2405 |
# fluid midi router rule
|
| 2406 |
class fluid_midi_router_t(Structure):
|
|
|
|
| 2462 |
('rule', c_void_p, 1),
|
| 2463 |
('type', c_int, 1))
|
| 2464 |
|
| 2465 |
+
# fluid file renderer
|
| 2466 |
+
new_fluid_file_renderer = cfunc('new_fluid_file_renderer', c_void_p,
|
| 2467 |
+
('synth', c_void_p, 1))
|
| 2468 |
+
|
| 2469 |
+
delete_fluid_file_renderer = cfunc('delete_fluid_file_renderer', None,
|
| 2470 |
+
('renderer', c_void_p, 1))
|
| 2471 |
+
|
| 2472 |
+
fluid_file_renderer_process_block = cfunc('fluid_file_renderer_process_block', c_int,
|
| 2473 |
+
('render', c_void_p, 1))
|
| 2474 |
+
|
| 2475 |
# fluidsynth 2.x
|
| 2476 |
new_fluid_cmd_handler=cfunc('new_fluid_cmd_handler', c_void_p,
|
| 2477 |
('synth', c_void_p, 1),
|
|
|
|
| 2546 |
self.audio_driver = None
|
| 2547 |
self.midi_driver = None
|
| 2548 |
self.router = None
|
| 2549 |
+
self.custom_router_callback = None
|
| 2550 |
def setting(self, opt, val):
|
| 2551 |
"""change an arbitrary synth setting, type-smart"""
|
| 2552 |
if isinstance(val, (str, bytes)):
|
|
|
|
| 2582 |
see http://www.fluidsynth.org/api/fluidsettings.xml for allowed values and defaults by platform
|
| 2583 |
"""
|
| 2584 |
driver = driver or self.get_setting('audio.driver')
|
| 2585 |
+
device = device or self.get_setting(f'audio.{driver}.device')
|
| 2586 |
midi_driver = midi_driver or self.get_setting('midi.driver')
|
| 2587 |
|
| 2588 |
self.setting('audio.driver', driver)
|
| 2589 |
+
self.setting(f'audio.{driver}.device', device)
|
| 2590 |
self.audio_driver = new_fluid_audio_driver(self.settings, self.synth)
|
| 2591 |
self.setting('midi.driver', midi_driver)
|
| 2592 |
self.router = new_fluid_midi_router(self.settings, fluid_synth_handle_midi_event, self.synth)
|
|
|
|
| 2594 |
new_fluid_cmd_handler(self.synth, self.router)
|
| 2595 |
else:
|
| 2596 |
fluid_synth_set_midi_router(self.synth, self.router)
|
| 2597 |
+
if midi_router is None: ## Use fluidsynth to create a MIDI event handler
|
| 2598 |
self.midi_driver = new_fluid_midi_driver(self.settings, fluid_midi_router_handle_midi_event, self.router)
|
| 2599 |
self.custom_router_callback = None
|
| 2600 |
else: ## Supply an external MIDI event handler
|
|
|
|
| 2605 |
def delete(self):
|
| 2606 |
if self.audio_driver:
|
| 2607 |
delete_fluid_audio_driver(self.audio_driver)
|
| 2608 |
+
if self.midi_driver:
|
| 2609 |
+
delete_fluid_midi_driver(self.midi_driver)
|
| 2610 |
delete_fluid_synth(self.synth)
|
| 2611 |
delete_fluid_settings(self.settings)
|
| 2612 |
def sfload(self, filename, update_midi_preset=0):
|
|
|
|
| 2651 |
return None
|
| 2652 |
return fluid_preset_get_name(preset).decode('ascii')
|
| 2653 |
else:
|
| 2654 |
+
return None
|
|
|
|
| 2655 |
def router_clear(self):
|
| 2656 |
if self.router is not None:
|
| 2657 |
fluid_midi_router_clear_rules(self.router)
|
|
|
|
| 2702 |
if fluid_synth_set_reverb is not None:
|
| 2703 |
return fluid_synth_set_reverb(self.synth, roomsize, damping, width, level)
|
| 2704 |
else:
|
| 2705 |
+
flags=0
|
| 2706 |
if roomsize>=0:
|
| 2707 |
+
flags+=0b0001
|
| 2708 |
if damping>=0:
|
| 2709 |
+
flags+=0b0010
|
| 2710 |
if width>=0:
|
| 2711 |
+
flags+=0b0100
|
| 2712 |
if level>=0:
|
| 2713 |
+
flags+=0b1000
|
| 2714 |
+
return fluid_synth_set_reverb_full(self.synth, flags, roomsize, damping, width, level)
|
| 2715 |
def set_chorus(self, nr=-1, level=-1.0, speed=-1.0, depth=-1.0, type=-1):
|
| 2716 |
"""
|
| 2717 |
nr Chorus voice count (0-99, CPU time consumption proportional to this value)
|
|
|
|
| 2764 |
if fluid_synth_set_chorus_level is not None:
|
| 2765 |
return fluid_synth_set_chorus_level(self.synth, level)
|
| 2766 |
else:
|
| 2767 |
+
return self.set_chorus(level=level)
|
| 2768 |
def set_chorus_speed(self, speed):
|
| 2769 |
if fluid_synth_set_chorus_speed is not None:
|
| 2770 |
return fluid_synth_set_chorus_speed(self.synth, speed)
|
| 2771 |
else:
|
| 2772 |
return self.set_chorus(speed=speed)
|
| 2773 |
+
def set_chorus_depth(self, depth_ms):
|
| 2774 |
if fluid_synth_set_chorus_depth is not None:
|
| 2775 |
+
return fluid_synth_set_chorus_depth(self.synth, depth_ms)
|
| 2776 |
else:
|
| 2777 |
+
return self.set_chorus(depth=depth_ms)
|
| 2778 |
def set_chorus_type(self, type):
|
| 2779 |
if fluid_synth_set_chorus_type is not None:
|
| 2780 |
return fluid_synth_set_chorus_type(self.synth, type)
|
|
|
|
| 2826 |
A pitch bend value of 0 is no pitch change from default.
|
| 2827 |
A value of -2048 is 1 semitone down.
|
| 2828 |
A value of 2048 is 1 semitone up.
|
| 2829 |
+
Maximum values are -8192 to +8191 (transposing by 4 semitones).
|
| 2830 |
|
| 2831 |
"""
|
| 2832 |
+
return fluid_synth_pitch_bend(self.synth, chan, max(0, min(val + 8192, 16383)))
|
| 2833 |
def cc(self, chan, ctrl, val):
|
| 2834 |
"""Send control change value
|
| 2835 |
|
|
|
|
| 2879 |
|
| 2880 |
"""
|
| 2881 |
return fluid_synth_write_s16_stereo(self.synth, len)
|
| 2882 |
+
def tuning_dump(self, bank, prog):
|
| 2883 |
+
"""Get tuning information for given bank and preset
|
| 2884 |
+
|
| 2885 |
+
Return value is an array of length 128 with tuning factors for each MIDI note.
|
| 2886 |
+
Tuning factor of 0.0 in each position is standard tuning. Measured in cents.
|
| 2887 |
+
"""
|
| 2888 |
+
pitch = (c_double * 128)()
|
| 2889 |
+
fluid_synth_tuning_dump(self.synth, bank, prog, None, 0, pitch)
|
| 2890 |
+
return pitch[:]
|
| 2891 |
|
| 2892 |
def midi_event_get_type(self, event):
|
| 2893 |
return fluid_midi_event_get_type(event)
|
|
|
|
| 2906 |
|
| 2907 |
def play_midi_file(self, filename):
|
| 2908 |
self.player = new_fluid_player(self.synth)
|
| 2909 |
+
if self.player is None:
|
| 2910 |
+
return FLUID_FAILED
|
| 2911 |
+
if self.custom_router_callback is not None:
|
| 2912 |
fluid_player_set_playback_callback(self.player, self.custom_router_callback, self.synth)
|
| 2913 |
status = fluid_player_add(self.player, filename.encode())
|
| 2914 |
+
if status == FLUID_FAILED:
|
| 2915 |
+
return status
|
| 2916 |
status = fluid_player_play(self.player)
|
| 2917 |
return status
|
| 2918 |
|
| 2919 |
def play_midi_stop(self):
|
| 2920 |
status = fluid_player_stop(self.player)
|
| 2921 |
+
if status == FLUID_FAILED:
|
| 2922 |
+
return status
|
| 2923 |
status = fluid_player_seek(self.player, 0)
|
| 2924 |
delete_fluid_player(self.player)
|
| 2925 |
return status
|
|
|
|
| 2927 |
def player_set_tempo(self, tempo_type, tempo):
|
| 2928 |
return fluid_player_set_tempo(self.player, tempo_type, tempo)
|
| 2929 |
|
| 2930 |
+
def midi2audio(self, midifile, audiofile = "output.wav"):
|
| 2931 |
+
"""Convert a midi file to an audio file"""
|
| 2932 |
+
self.setting("audio.file.name", audiofile)
|
| 2933 |
+
player = new_fluid_player(self.synth)
|
| 2934 |
+
fluid_player_add(player, midifile.encode())
|
| 2935 |
+
fluid_player_play(player)
|
| 2936 |
+
renderer = new_fluid_file_renderer(self.synth)
|
| 2937 |
+
while fluid_player_get_status(player) == FLUID_PLAYER_PLAYING:
|
| 2938 |
+
if fluid_file_renderer_process_block(renderer) != FLUID_OK:
|
| 2939 |
+
break
|
| 2940 |
+
delete_fluid_file_renderer(renderer)
|
| 2941 |
+
delete_fluid_player(player)
|
| 2942 |
+
|
| 2943 |
+
# flag values
|
| 2944 |
+
FLUID_MOD_POSITIVE = 0
|
| 2945 |
+
FLUID_MOD_NEGATIVE = 1
|
| 2946 |
+
FLUID_MOD_UNIPOLAR = 0
|
| 2947 |
+
FLUID_MOD_BIPOLAR = 2
|
| 2948 |
+
FLUID_MOD_LINEAR = 0
|
| 2949 |
+
FLUID_MOD_CONCAVE = 4
|
| 2950 |
+
FLUID_MOD_CONVEX = 8
|
| 2951 |
+
FLUID_MOD_SWITCH = 12
|
| 2952 |
+
FLUID_MOD_GC = 0
|
| 2953 |
+
FLUID_MOD_CC = 16
|
| 2954 |
+
FLUID_MOD_SIN = 0x80
|
| 2955 |
+
|
| 2956 |
+
# src values
|
| 2957 |
+
FLUID_MOD_NONE = 0
|
| 2958 |
+
FLUID_MOD_VELOCITY = 2
|
| 2959 |
+
FLUID_MOD_KEY = 3
|
| 2960 |
+
FLUID_MOD_KEYPRESSURE = 10
|
| 2961 |
+
FLUID_MOD_CHANNELPRESSURE = 13
|
| 2962 |
+
FLUID_MOD_PITCHWHEEL = 14
|
| 2963 |
+
FLUID_MOD_PITCHWHEELSENS = 16
|
| 2964 |
+
|
| 2965 |
+
# Transforms
|
| 2966 |
+
FLUID_MOD_TRANSFORM_LINEAR = 0
|
| 2967 |
+
FLUID_MOD_TRANSFORM_ABS = 2
|
| 2968 |
+
|
| 2969 |
+
class Modulator:
|
| 2970 |
+
def __init__(self):
|
| 2971 |
+
"""Create new modulator object"""
|
| 2972 |
+
self.mod = new_fluid_mod()
|
| 2973 |
+
|
| 2974 |
+
def clone(self, src):
|
| 2975 |
+
response = fluid_mod_clone(self.mod, src)
|
| 2976 |
+
if response == FLUID_FAILED:
|
| 2977 |
+
raise Exception("Modulation clone failed")
|
| 2978 |
+
return response
|
| 2979 |
+
|
| 2980 |
+
def get_amount(self):
|
| 2981 |
+
response = fluid_mod_get_amount(self.mod)
|
| 2982 |
+
if response == FLUID_FAILED:
|
| 2983 |
+
raise Exception("Modulation amount get failed")
|
| 2984 |
+
return response
|
| 2985 |
+
|
| 2986 |
+
def get_dest(self):
|
| 2987 |
+
response = fluid_mod_get_dest(self.mod)
|
| 2988 |
+
if response == FLUID_FAILED:
|
| 2989 |
+
raise Exception("Modulation destination get failed")
|
| 2990 |
+
return response
|
| 2991 |
+
|
| 2992 |
+
def get_flags1(self):
|
| 2993 |
+
response = fluid_mod_get_flags1(self.mod)
|
| 2994 |
+
if response == FLUID_FAILED:
|
| 2995 |
+
raise Exception("Modulation flags1 get failed")
|
| 2996 |
+
return response
|
| 2997 |
+
|
| 2998 |
+
def get_flags2(self):
|
| 2999 |
+
response = fluid_mod_get_flags2(self.mod)
|
| 3000 |
+
if response == FLUID_FAILED:
|
| 3001 |
+
raise Exception("Modulation flags2 get failed")
|
| 3002 |
+
return response
|
| 3003 |
+
|
| 3004 |
+
def get_source1(self):
|
| 3005 |
+
response = fluid_mod_get_source1(self.mod)
|
| 3006 |
+
if response == FLUID_FAILED:
|
| 3007 |
+
raise Exception("Modulation source1 get failed")
|
| 3008 |
+
return response
|
| 3009 |
+
|
| 3010 |
+
def get_source2(self):
|
| 3011 |
+
response = fluid_mod_get_source2(self.mod)
|
| 3012 |
+
if response == FLUID_FAILED:
|
| 3013 |
+
raise Exception("Modulation source2 get failed")
|
| 3014 |
+
return response
|
| 3015 |
+
|
| 3016 |
+
def get_transform(self):
|
| 3017 |
+
response = fluid_mod_get_transform(self.mod)
|
| 3018 |
+
if response == FLUID_FAILED:
|
| 3019 |
+
raise Exception("Modulation transform get failed")
|
| 3020 |
+
return response
|
| 3021 |
+
|
| 3022 |
+
def has_dest(self, gen):
|
| 3023 |
+
response = fluid_mod_has_dest(self.mod, gen)
|
| 3024 |
+
if response == FLUID_FAILED:
|
| 3025 |
+
raise Exception("Modulation has destination check failed")
|
| 3026 |
+
return response
|
| 3027 |
+
|
| 3028 |
+
def has_source(self, cc, ctrl):
|
| 3029 |
+
response = fluid_mod_has_source(self.mod, cc, ctrl)
|
| 3030 |
+
if response == FLUID_FAILED:
|
| 3031 |
+
raise Exception("Modulation has source check failed")
|
| 3032 |
+
return response
|
| 3033 |
+
|
| 3034 |
+
def set_amount(self, amount):
|
| 3035 |
+
response = fluid_mod_set_amount(self.mod, amount)
|
| 3036 |
+
if response == FLUID_FAILED:
|
| 3037 |
+
raise Exception("Modulation set amount failed")
|
| 3038 |
+
return response
|
| 3039 |
+
|
| 3040 |
+
def set_dest(self, dest):
|
| 3041 |
+
response = fluid_mod_set_dest(self.mod, dest)
|
| 3042 |
+
if response == FLUID_FAILED:
|
| 3043 |
+
raise Exception("Modulation set dest failed")
|
| 3044 |
+
return response
|
| 3045 |
+
|
| 3046 |
+
def set_source1(self, src, flags):
|
| 3047 |
+
response = fluid_mod_set_source1(self.mod, src, flags)
|
| 3048 |
+
if response == FLUID_FAILED:
|
| 3049 |
+
raise Exception("Modulation set source 1 failed")
|
| 3050 |
+
return response
|
| 3051 |
+
|
| 3052 |
+
def set_source2(self, src, flags):
|
| 3053 |
+
response = fluid_mod_set_source2(self.mod, src, flags)
|
| 3054 |
+
if response == FLUID_FAILED:
|
| 3055 |
+
raise Exception("Modulation set source 2 failed")
|
| 3056 |
+
return response
|
| 3057 |
+
|
| 3058 |
+
def set_transform(self, type):
|
| 3059 |
+
response = fluid_mod_set_transform(self.mod, type)
|
| 3060 |
+
if response == FLUID_FAILED:
|
| 3061 |
+
raise Exception("Modulation set transform failed")
|
| 3062 |
+
return response
|
| 3063 |
+
|
| 3064 |
+
def sizeof(self):
|
| 3065 |
+
response = fluid_mod_sizeof()
|
| 3066 |
+
if response == FLUID_FAILED:
|
| 3067 |
+
raise Exception("Modulation sizeof failed")
|
| 3068 |
+
return response
|
| 3069 |
|
| 3070 |
+
def test_identity(self, mod2):
|
| 3071 |
+
response = fluid_mod_sizeof(self.mod, mod2)
|
| 3072 |
+
if response == FLUID_FAILED:
|
| 3073 |
+
raise Exception("Modulation identity check failed")
|
| 3074 |
+
return response
|
| 3075 |
|
| 3076 |
class Sequencer:
|
| 3077 |
def __init__(self, time_scale=1000, use_system_timer=True):
|
|
|
|
| 3088 |
def register_fluidsynth(self, synth):
|
| 3089 |
response = fluid_sequencer_register_fluidsynth(self.sequencer, synth.synth)
|
| 3090 |
if response == FLUID_FAILED:
|
| 3091 |
+
raise Exception("Registering fluid synth failed")
|
| 3092 |
return response
|
| 3093 |
|
| 3094 |
def register_client(self, name, callback, data=None):
|
| 3095 |
c_callback = CFUNCTYPE(None, c_uint, c_void_p, c_void_p, c_void_p)(callback)
|
| 3096 |
response = fluid_sequencer_register_client(self.sequencer, name.encode(), c_callback, data)
|
| 3097 |
if response == FLUID_FAILED:
|
| 3098 |
+
raise Exception("Registering client failed")
|
| 3099 |
|
| 3100 |
# store in a list to prevent garbage collection
|
| 3101 |
self.client_callbacks.append(c_callback)
|
|
|
|
| 3135 |
def _schedule_event(self, evt, time, absolute=True):
|
| 3136 |
response = fluid_sequencer_send_at(self.sequencer, evt, time, absolute)
|
| 3137 |
if response == FLUID_FAILED:
|
| 3138 |
+
raise Exception("Scheduling event failed")
|
| 3139 |
|
| 3140 |
def get_tick(self):
|
| 3141 |
return fluid_sequencer_get_tick(self.sequencer)
|
|
|
|
| 3154 |
|
| 3155 |
"""
|
| 3156 |
import numpy
|
| 3157 |
+
return (data.astype(numpy.int16)).tobytes()
|
| 3158 |
|
| 3159 |
#===============================================================================
|
| 3160 |
|
| 3161 |
import numpy as np
|
| 3162 |
import wave
|
| 3163 |
|
| 3164 |
+
#===============================================================================
|
| 3165 |
+
|
| 3166 |
+
def normalize_audio(audio: np.ndarray,
|
| 3167 |
+
method: str = 'peak',
|
| 3168 |
+
target_level_db: float = -1.0,
|
| 3169 |
+
per_channel: bool = False,
|
| 3170 |
+
eps: float = 1e-9
|
| 3171 |
+
) -> np.ndarray:
|
| 3172 |
+
|
| 3173 |
+
"""
|
| 3174 |
+
Normalize audio to a target dBFS level.
|
| 3175 |
+
|
| 3176 |
+
Parameters
|
| 3177 |
+
----------
|
| 3178 |
+
audio : np.ndarray
|
| 3179 |
+
Float-valued array in range [-1, 1] with shape (channels, samples)
|
| 3180 |
+
or (samples,) for mono.
|
| 3181 |
+
method : {'peak', 'rms'}
|
| 3182 |
+
- 'peak': scale so that max(|audio|) = target_level_lin
|
| 3183 |
+
- 'rms' : scale so that RMS(audio) = target_level_lin
|
| 3184 |
+
target_level_db : float
|
| 3185 |
+
Desired output level, in dBFS (0 dBFS = max digital full scale).
|
| 3186 |
+
e.g. -1.0 dBFS means ~0.8913 linear gain.
|
| 3187 |
+
per_channel : bool
|
| 3188 |
+
If True, normalize each channel independently. Otherwise, use a
|
| 3189 |
+
global measure across all channels.
|
| 3190 |
+
eps : float
|
| 3191 |
+
Small constant to avoid division by zero.
|
| 3192 |
+
|
| 3193 |
+
Returns
|
| 3194 |
+
-------
|
| 3195 |
+
normalized : np.ndarray
|
| 3196 |
+
Audio array of same shape, scaled so that levels meet the target.
|
| 3197 |
+
"""
|
| 3198 |
+
|
| 3199 |
+
# Convert target dB to linear gain
|
| 3200 |
+
target_lin = 10 ** (target_level_db / 20.0)
|
| 3201 |
+
|
| 3202 |
+
# Ensure audio is float
|
| 3203 |
+
audio = audio.astype(np.float32)
|
| 3204 |
+
|
| 3205 |
+
# if mono, make it (1, N)
|
| 3206 |
+
if audio.ndim == 1:
|
| 3207 |
+
audio = audio[np.newaxis, :]
|
| 3208 |
+
|
| 3209 |
+
# Choose measurement axis
|
| 3210 |
+
axis = 1 if per_channel else None
|
| 3211 |
+
|
| 3212 |
+
if method == 'peak':
|
| 3213 |
+
# Compute peak per channel or global
|
| 3214 |
+
peak = np.max(np.abs(audio), axis=axis, keepdims=True)
|
| 3215 |
+
peak = np.maximum(peak, eps)
|
| 3216 |
+
scales = target_lin / peak
|
| 3217 |
+
|
| 3218 |
+
elif method == 'rms':
|
| 3219 |
+
# Compute RMS per channel or global
|
| 3220 |
+
rms = np.sqrt(np.mean(audio ** 2, axis=axis, keepdims=True))
|
| 3221 |
+
rms = np.maximum(rms, eps)
|
| 3222 |
+
scales = target_lin / rms
|
| 3223 |
+
|
| 3224 |
+
else:
|
| 3225 |
+
raise ValueError(f"Unsupported method '{method}'; choose 'peak' or 'rms'.")
|
| 3226 |
+
|
| 3227 |
+
# Broadcast scales back to audio shape
|
| 3228 |
+
normalized = audio * scales
|
| 3229 |
+
|
| 3230 |
+
# Clip just in case of rounding
|
| 3231 |
+
return np.clip(normalized, -1.0, 1.0)
|
| 3232 |
+
|
| 3233 |
+
#===============================================================================
|
| 3234 |
+
|
| 3235 |
def midi_opus_to_colab_audio(midi_opus,
|
| 3236 |
soundfont_path='/usr/share/sounds/sf2/FluidR3_GM.sf2',
|
| 3237 |
sample_rate=16000, # 44100
|
| 3238 |
+
volume_level_db=-1,
|
| 3239 |
trim_silence=True,
|
| 3240 |
silence_threshold=0.1,
|
| 3241 |
output_for_gradio=False,
|
| 3242 |
write_audio_to_WAV=''
|
| 3243 |
):
|
| 3244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3245 |
if midi_opus[1]:
|
| 3246 |
|
| 3247 |
+
ticks_per_beat, *tracks = midi_opus
|
| 3248 |
+
if not tracks:
|
| 3249 |
+
return None
|
| 3250 |
+
|
| 3251 |
+
# Flatten & convert delta-times to absolute-time
|
| 3252 |
+
events = []
|
| 3253 |
+
for track in tracks:
|
| 3254 |
+
abs_t = 0
|
| 3255 |
+
for name, dt, *data in track:
|
| 3256 |
+
abs_t += dt
|
| 3257 |
+
events.append([name, abs_t, *data])
|
| 3258 |
+
events.sort(key=lambda e: e[1])
|
| 3259 |
+
|
| 3260 |
+
# Setup FluidSynth
|
| 3261 |
+
fl = Synth(samplerate=float(sample_rate))
|
| 3262 |
+
sfid = fl.sfload(soundfont_path)
|
| 3263 |
+
for chan in range(16):
|
| 3264 |
+
# channel 9 = percussion GM bank 128
|
| 3265 |
+
fl.program_select(chan, sfid, 128 if chan == 9 else 0, 0)
|
| 3266 |
+
|
| 3267 |
+
# Playback vars
|
| 3268 |
+
tempo = int((60 / 120) * 1e6) # default 120bpm
|
| 3269 |
+
last_t = 0
|
| 3270 |
+
ss = np.empty((0, 2), dtype=np.int16)
|
| 3271 |
+
|
| 3272 |
+
for name, cur_t, *data in events:
|
| 3273 |
+
# compute how many samples have passed since the last event
|
| 3274 |
+
delta_ticks = cur_t - last_t
|
| 3275 |
+
last_t = cur_t
|
| 3276 |
+
dt_seconds = (delta_ticks / ticks_per_beat) * (tempo / 1e6)
|
| 3277 |
+
sample_len = int(dt_seconds * sample_rate)
|
| 3278 |
+
if sample_len > 0:
|
| 3279 |
+
buf = fl.get_samples(sample_len).reshape(-1, 2)
|
| 3280 |
+
ss = np.concatenate([ss, buf], axis=0)
|
| 3281 |
+
|
| 3282 |
+
# Dispatch every known event
|
| 3283 |
+
if name == "note_on" and data[2] > 0:
|
| 3284 |
+
chan, note, vel = data
|
| 3285 |
+
fl.noteon(chan, note, vel)
|
| 3286 |
+
|
| 3287 |
+
elif name == "note_off" or (name == "note_on" and data[2] == 0):
|
| 3288 |
+
chan, note = data[:2]
|
| 3289 |
+
fl.noteoff(chan, note)
|
| 3290 |
+
|
| 3291 |
+
elif name == "patch_change":
|
| 3292 |
+
chan, patch = data[:2]
|
| 3293 |
+
bank = 128 if chan == 9 else 0
|
| 3294 |
+
fl.program_select(chan, sfid, bank, patch)
|
| 3295 |
+
|
| 3296 |
+
elif name == "control_change":
|
| 3297 |
+
chan, ctrl, val = data[:3]
|
| 3298 |
+
fl.cc(chan, ctrl, val)
|
| 3299 |
+
|
| 3300 |
+
elif name == "key_after_touch":
|
| 3301 |
+
chan, note, vel = data
|
| 3302 |
+
fl.key_pressure(chan, note, vel)
|
| 3303 |
+
|
| 3304 |
+
elif name == "channel_after_touch":
|
| 3305 |
+
chan, vel = data
|
| 3306 |
+
fl.channel_pressure(chan, vel)
|
| 3307 |
+
|
| 3308 |
+
elif name == "pitch_wheel_change":
|
| 3309 |
+
chan, wheel = data
|
| 3310 |
+
fl.pitch_bend(chan, wheel)
|
| 3311 |
+
|
| 3312 |
+
elif name == "song_position":
|
| 3313 |
+
# song_pos = data[0]; # often not needed for playback
|
| 3314 |
+
pass
|
| 3315 |
+
|
| 3316 |
+
elif name == "song_select":
|
| 3317 |
+
# song_number = data[0]
|
| 3318 |
+
pass
|
| 3319 |
+
|
| 3320 |
+
elif name == "tune_request":
|
| 3321 |
+
# typically resets tuning; FS handles internally
|
| 3322 |
+
pass
|
| 3323 |
+
|
| 3324 |
+
elif name in ("sysex_f0", "sysex_f7"):
|
| 3325 |
+
raw_bytes = data[0]
|
| 3326 |
+
fl.sysex(raw_bytes)
|
| 3327 |
+
|
| 3328 |
+
# Meta events & others—no direct audio effect, so we skip or log
|
| 3329 |
+
elif name in (
|
| 3330 |
+
"set_tempo", # handled below
|
| 3331 |
+
"end_track",
|
| 3332 |
+
"text_event", "text_event_08", "text_event_09", "text_event_0a",
|
| 3333 |
+
"text_event_0b", "text_event_0c", "text_event_0d", "text_event_0e", "text_event_0f",
|
| 3334 |
+
"copyright_text_event", "track_name", "instrument_name",
|
| 3335 |
+
"lyric", "marker", "cue_point",
|
| 3336 |
+
"smpte_offset", "time_signature", "key_signature",
|
| 3337 |
+
"sequencer_specific", "raw_meta_event"
|
| 3338 |
+
):
|
| 3339 |
+
if name == "set_tempo":
|
| 3340 |
+
tempo = data[0]
|
| 3341 |
+
# else: skip all other meta & text; you could hook in logging here
|
| 3342 |
+
continue
|
| 3343 |
+
|
| 3344 |
+
else:
|
| 3345 |
+
# unknown event type
|
| 3346 |
+
continue
|
| 3347 |
+
|
| 3348 |
+
# Cleanup synth
|
| 3349 |
+
fl.delete()
|
| 3350 |
+
|
| 3351 |
+
if ss.size:
|
| 3352 |
+
maxv = np.abs(ss).max()
|
| 3353 |
+
if maxv:
|
| 3354 |
+
ss = (ss / maxv) * np.iinfo(np.int16).max
|
| 3355 |
+
ss = ss.astype(np.int16)
|
| 3356 |
+
|
| 3357 |
+
# Optional trimming of trailing silence
|
| 3358 |
+
if trim_silence and ss.size:
|
| 3359 |
+
thresh = np.std(np.abs(ss)) * silence_threshold
|
| 3360 |
+
idx = np.where(np.abs(ss) > thresh)[0]
|
| 3361 |
+
if idx.size:
|
| 3362 |
+
ss = ss[: idx[-1] + 1]
|
| 3363 |
+
|
| 3364 |
+
# For Gradio you might want raw int16 PCM
|
| 3365 |
+
if output_for_gradio:
|
| 3366 |
+
return ss
|
| 3367 |
+
|
| 3368 |
+
# Swap to (channels, samples) and normalize for playback
|
| 3369 |
+
ss = ss.T
|
| 3370 |
+
raw_audio = normalize_audio(ss, target_level_db=volume_level_db)
|
| 3371 |
+
|
| 3372 |
+
# Optionally write WAV to disk
|
| 3373 |
+
if write_audio_to_WAV:
|
| 3374 |
+
wav_name = midi_file.rsplit('.', 1)[0] + '.wav'
|
| 3375 |
+
pcm = np.int16(raw_audio.T / np.max(np.abs(raw_audio)) * 32767)
|
| 3376 |
+
with wave.open(wav_name, 'wb') as wf:
|
| 3377 |
+
wf.setframerate(sample_rate)
|
| 3378 |
+
wf.setsampwidth(2)
|
| 3379 |
+
wf.setnchannels(pcm.shape[1])
|
| 3380 |
+
wf.writeframes(pcm.tobytes())
|
| 3381 |
+
|
| 3382 |
+
return raw_audio
|
| 3383 |
|
| 3384 |
else:
|
| 3385 |
return None
|
| 3386 |
|
| 3387 |
+
#===============================================================================
|
| 3388 |
+
|
| 3389 |
+
def midi_to_colab_audio(midi_file,
|
| 3390 |
+
soundfont_path='/usr/share/sounds/sf2/FluidR3_GM.sf2',
|
| 3391 |
+
sample_rate=16000,
|
| 3392 |
+
volume_level_db=-1,
|
| 3393 |
trim_silence=True,
|
| 3394 |
silence_threshold=0.1,
|
| 3395 |
output_for_gradio=False,
|
| 3396 |
write_audio_to_WAV=False
|
| 3397 |
+
):
|
| 3398 |
+
"""
|
|
|
|
|
|
|
| 3399 |
Returns raw audio to pass to IPython.disaply.Audio func
|
| 3400 |
|
| 3401 |
Example usage:
|
|
|
|
| 3403 |
from IPython.display import Audio
|
| 3404 |
|
| 3405 |
display(Audio(raw_audio, rate=16000, normalize=False))
|
| 3406 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3407 |
|
| 3408 |
+
# Read and decode MIDI → opus event list
|
| 3409 |
+
ticks_per_beat, *tracks = midi2opus(open(midi_file, 'rb').read())
|
| 3410 |
+
if not tracks:
|
| 3411 |
+
return None
|
| 3412 |
|
| 3413 |
+
# Flatten & convert delta-times to absolute-time
|
| 3414 |
+
events = []
|
| 3415 |
+
for track in tracks:
|
| 3416 |
+
abs_t = 0
|
| 3417 |
+
for name, dt, *data in track:
|
| 3418 |
+
abs_t += dt
|
| 3419 |
+
events.append([name, abs_t, *data])
|
| 3420 |
+
events.sort(key=lambda e: e[1])
|
| 3421 |
+
|
| 3422 |
+
# Setup FluidSynth
|
| 3423 |
+
fl = Synth(samplerate=float(sample_rate))
|
| 3424 |
+
sfid = fl.sfload(soundfont_path)
|
| 3425 |
+
for chan in range(16):
|
| 3426 |
+
# channel 9 = percussion GM bank 128
|
| 3427 |
+
fl.program_select(chan, sfid, 128 if chan == 9 else 0, 0)
|
| 3428 |
+
|
| 3429 |
+
# Playback vars
|
| 3430 |
+
tempo = int((60 / 120) * 1e6) # default 120bpm
|
| 3431 |
+
last_t = 0
|
| 3432 |
+
ss = np.empty((0, 2), dtype=np.int16)
|
| 3433 |
+
|
| 3434 |
+
for name, cur_t, *data in events:
|
| 3435 |
+
# compute how many samples have passed since the last event
|
| 3436 |
+
delta_ticks = cur_t - last_t
|
| 3437 |
+
last_t = cur_t
|
| 3438 |
+
dt_seconds = (delta_ticks / ticks_per_beat) * (tempo / 1e6)
|
| 3439 |
+
sample_len = int(dt_seconds * sample_rate)
|
| 3440 |
+
if sample_len > 0:
|
| 3441 |
+
buf = fl.get_samples(sample_len).reshape(-1, 2)
|
| 3442 |
+
ss = np.concatenate([ss, buf], axis=0)
|
| 3443 |
+
|
| 3444 |
+
# Dispatch every known event
|
| 3445 |
+
if name == "note_on" and data[2] > 0:
|
| 3446 |
+
chan, note, vel = data
|
| 3447 |
+
fl.noteon(chan, note, vel)
|
| 3448 |
+
|
| 3449 |
+
elif name == "note_off" or (name == "note_on" and data[2] == 0):
|
| 3450 |
+
chan, note = data[:2]
|
| 3451 |
+
fl.noteoff(chan, note)
|
| 3452 |
+
|
| 3453 |
+
elif name == "patch_change":
|
| 3454 |
+
chan, patch = data[:2]
|
| 3455 |
+
bank = 128 if chan == 9 else 0
|
| 3456 |
+
fl.program_select(chan, sfid, bank, patch)
|
| 3457 |
+
|
| 3458 |
+
elif name == "control_change":
|
| 3459 |
+
chan, ctrl, val = data[:3]
|
| 3460 |
+
fl.cc(chan, ctrl, val)
|
| 3461 |
+
|
| 3462 |
+
elif name == "key_after_touch":
|
| 3463 |
+
chan, note, vel = data
|
| 3464 |
+
fl.key_pressure(chan, note, vel)
|
| 3465 |
+
|
| 3466 |
+
elif name == "channel_after_touch":
|
| 3467 |
+
chan, vel = data
|
| 3468 |
+
fl.channel_pressure(chan, vel)
|
| 3469 |
+
|
| 3470 |
+
elif name == "pitch_wheel_change":
|
| 3471 |
+
chan, wheel = data
|
| 3472 |
+
fl.pitch_bend(chan, wheel)
|
| 3473 |
+
|
| 3474 |
+
elif name == "song_position":
|
| 3475 |
+
# song_pos = data[0]; # often not needed for playback
|
| 3476 |
+
pass
|
| 3477 |
+
|
| 3478 |
+
elif name == "song_select":
|
| 3479 |
+
# song_number = data[0]
|
| 3480 |
+
pass
|
| 3481 |
+
|
| 3482 |
+
elif name == "tune_request":
|
| 3483 |
+
# typically resets tuning; FS handles internally
|
| 3484 |
+
pass
|
| 3485 |
+
|
| 3486 |
+
elif name in ("sysex_f0", "sysex_f7"):
|
| 3487 |
+
raw_bytes = data[0]
|
| 3488 |
+
fl.sysex(raw_bytes)
|
| 3489 |
+
|
| 3490 |
+
# Meta events & others—no direct audio effect, so we skip or log
|
| 3491 |
+
elif name in (
|
| 3492 |
+
"set_tempo", # handled below
|
| 3493 |
+
"end_track",
|
| 3494 |
+
"text_event", "text_event_08", "text_event_09", "text_event_0a",
|
| 3495 |
+
"text_event_0b", "text_event_0c", "text_event_0d", "text_event_0e", "text_event_0f",
|
| 3496 |
+
"copyright_text_event", "track_name", "instrument_name",
|
| 3497 |
+
"lyric", "marker", "cue_point",
|
| 3498 |
+
"smpte_offset", "time_signature", "key_signature",
|
| 3499 |
+
"sequencer_specific", "raw_meta_event"
|
| 3500 |
+
):
|
| 3501 |
+
if name == "set_tempo":
|
| 3502 |
+
tempo = data[0]
|
| 3503 |
+
# else: skip all other meta & text; you could hook in logging here
|
| 3504 |
+
continue
|
| 3505 |
|
| 3506 |
+
else:
|
| 3507 |
+
# unknown event type
|
| 3508 |
+
continue
|
| 3509 |
|
| 3510 |
+
# Cleanup synth
|
| 3511 |
+
fl.delete()
|
| 3512 |
|
| 3513 |
+
if ss.size:
|
| 3514 |
+
maxv = np.abs(ss).max()
|
| 3515 |
+
if maxv:
|
| 3516 |
+
ss = (ss / maxv) * np.iinfo(np.int16).max
|
| 3517 |
+
ss = ss.astype(np.int16)
|
| 3518 |
|
| 3519 |
+
# Optional trimming of trailing silence
|
| 3520 |
+
if trim_silence and ss.size:
|
| 3521 |
+
thresh = np.std(np.abs(ss)) * silence_threshold
|
| 3522 |
+
idx = np.where(np.abs(ss) > thresh)[0]
|
| 3523 |
+
if idx.size:
|
| 3524 |
+
ss = ss[: idx[-1] + 1]
|
| 3525 |
|
| 3526 |
+
# For Gradio you might want raw int16 PCM
|
| 3527 |
+
if output_for_gradio:
|
| 3528 |
+
return ss
|
| 3529 |
|
| 3530 |
+
# Swap to (channels, samples) and normalize for playback
|
| 3531 |
+
ss = ss.T
|
| 3532 |
+
raw_audio = normalize_audio(ss, target_level_db=volume_level_db)
|
| 3533 |
|
| 3534 |
+
# Optionally write WAV to disk
|
| 3535 |
+
if write_audio_to_WAV:
|
| 3536 |
+
wav_name = midi_file.rsplit('.', 1)[0] + '.wav'
|
| 3537 |
+
pcm = np.int16(raw_audio.T / np.max(np.abs(raw_audio)) * 32767)
|
| 3538 |
+
with wave.open(wav_name, 'wb') as wf:
|
| 3539 |
wf.setframerate(sample_rate)
|
| 3540 |
wf.setsampwidth(2)
|
| 3541 |
+
wf.setnchannels(pcm.shape[1])
|
| 3542 |
+
wf.writeframes(pcm.tobytes())
|
| 3543 |
+
|
| 3544 |
+
return raw_audio
|
| 3545 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3546 |
#===================================================================================================================
|