asigalov61 commited on
Commit
7aa858c
·
verified ·
1 Parent(s): 3c27b04

Upload 2 files

Browse files
Files changed (2) hide show
  1. TMIDIX.py +0 -0
  2. midi_to_colab_audio.py +775 -228
TMIDIX.py CHANGED
The diff for this file is too large to render. See raw diff
 
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 1.0
9
  #
10
- # Includes full source code of MIDI, pyfluidsynth, and midi_synthesizer Python modules
11
  #
12
- # Original source code for all modules was retrieved on 10/23/2023
13
  #
14
  # Project Los Angeles
15
- # Tegridy Code 2023
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 <[email protected]>
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
- # A short circuited or expression to find the FluidSynth library
1798
- # (mostly needed for Windows distributions of libfluidsynth supplied with QSynth)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- lib = find_library('fluidsynth') or \
1806
- find_library('libfluidsynth') or \
1807
- find_library('libfluidsynth-3') or \
1808
- find_library('libfluidsynth-2') or \
1809
- find_library('libfluidsynth-1')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.1'
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.%s.device' % driver)
2455
  midi_driver = midi_driver or self.get_setting('midi.driver')
2456
 
2457
  self.setting('audio.driver', driver)
2458
- self.setting('audio.%s.device' % driver, device)
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 == None: ## Use fluidsynth to create a MIDI event handler
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
- (sfontid, banknum, presetnum, presetname) = self.channel_info(chan)
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
- set=0
2574
  if roomsize>=0:
2575
- set+=0b0001
2576
  if damping>=0:
2577
- set+=0b0010
2578
  if width>=0:
2579
- set+=0b0100
2580
  if level>=0:
2581
- set+=0b1000
2582
- return fluid_synth_set_reverb_full(self.synth, set, roomsize, damping, width, level)
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(leve=level)
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, depth):
2642
  if fluid_synth_set_chorus_depth is not None:
2643
- return fluid_synth_set_chorus_depth(self.synth, depth)
2644
  else:
2645
- return self.set_chorus(depth=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 +8192 (transposing by 4 semitones).
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, pitch):
2751
- return fluid_synth_tuning_dump(self.synth, bank, prog, name.encode(), length(name), pitch)
 
 
 
 
 
 
 
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 == None: return FLUID_FAILED
2771
- if self.custom_router_callback != None:
 
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: return status
 
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: return status
 
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 Error("Registering fluid synth failed")
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 Error("Registering client failed")
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 Error("Scheduling event failed")
2853
 
2854
  def get_tick(self):
2855
  return fluid_sequencer_get_tick(self.sequencer)
@@ -2868,123 +3154,307 @@ def raw_audio_string(data):
2868
 
2869
  """
2870
  import numpy
2871
- return (data.astype(numpy.int16)).tostring()
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
- volume_scale=10,
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
- ticks_per_beat = midi_opus[0]
2898
- event_list = []
2899
- for track_idx, track in enumerate(midi_opus[1:]):
2900
- abs_t = 0
2901
- for event in track:
2902
- abs_t += event[1]
2903
- event_new = [*event]
2904
- event_new[1] = abs_t
2905
- event_list.append(event_new)
2906
- event_list = sorted(event_list, key=lambda e: e[1])
2907
-
2908
- tempo = int((60 / 120) * 10 ** 6) # default 120 bpm
2909
- ss = np.empty((0, 2), dtype=np.int16)
2910
- fl = Synth(samplerate=float(sample_rate))
2911
- sfid = fl.sfload(soundfont_path)
2912
- last_t = 0
2913
- for c in range(16):
2914
- fl.program_select(c, sfid, 128 if c == 9 else 0, 0)
2915
- for event in event_list:
2916
- name = event[0]
2917
- sample_len = int(((event[1] / ticks_per_beat) * tempo / (10 ** 6)) * sample_rate)
2918
- sample_len -= int(((last_t / ticks_per_beat) * tempo / (10 ** 6)) * sample_rate)
2919
- last_t = event[1]
2920
- if sample_len > 0:
2921
- sample = fl.get_samples(sample_len).reshape(sample_len, 2)
2922
- ss = np.concatenate([ss, sample])
2923
- if name == "set_tempo":
2924
- tempo = event[2]
2925
- elif name == "patch_change":
2926
- c, p = event[2:4]
2927
- fl.program_select(c, sfid, 128 if c == 9 else 0, p)
2928
- elif name == "control_change":
2929
- c, cc, v = event[2:5]
2930
- fl.cc(c, cc, v)
2931
- elif name == "note_on" and event[3] > 0:
2932
- c, p, v = event[2:5]
2933
- fl.noteon(c, p, v)
2934
- elif name == "note_off" or (name == "note_on" and event[3] == 0):
2935
- c, p = event[2:4]
2936
- fl.noteoff(c, p)
2937
-
2938
- fl.delete()
2939
- if ss.shape[0] > 0:
2940
- max_val = np.abs(ss).max()
2941
- if max_val != 0:
2942
- ss = (ss / max_val) * np.iinfo(np.int16).max
2943
- ss = ss.astype(np.int16)
2944
-
2945
- if trim_silence:
2946
- threshold = np.std(np.abs(ss)) * silence_threshold
2947
- exceeded_thresh = np.abs(ss) > threshold
2948
- if np.any(exceeded_thresh):
2949
- last_idx = np.where(exceeded_thresh)[0][-1]
2950
- ss = ss[:last_idx+1]
2951
-
2952
- if output_for_gradio:
2953
- return ss
2954
-
2955
- ss = ss.swapaxes(1, 0)
2956
 
2957
- raw_audio = normalize_volume(ss, volume_scale)
2958
-
2959
- if write_audio_to_WAV != '':
2960
 
2961
- r_audio = raw_audio.T
 
2962
 
2963
- r_audio = np.int16(r_audio / np.max(np.abs(r_audio)) * 32767)
 
 
 
 
2964
 
2965
- with wave.open(write_audio_to_WAV, 'w') as wf:
2966
- wf.setframerate(sample_rate)
2967
- wf.setsampwidth(2)
2968
- wf.setnchannels(r_audio.shape[1])
2969
- wf.writeframes(r_audio)
 
 
 
2970
 
2971
- return raw_audio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2972
 
2973
  else:
2974
  return None
2975
 
2976
- def midi_to_colab_audio(midi_file,
2977
- soundfont_path='/usr/share/sounds/sf2/FluidR3_GM.sf2',
2978
- sample_rate=16000, # 44100
2979
- volume_scale=10,
 
 
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 +3462,176 @@ 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
- if midi_opus[1]:
 
 
 
3008
 
3009
- ticks_per_beat = midi_opus[0]
3010
- event_list = []
3011
- for track_idx, track in enumerate(midi_opus[1:]):
3012
- abs_t = 0
3013
- for event in track:
3014
- abs_t += event[1]
3015
- event_new = [*event]
3016
- event_new[1] = abs_t
3017
- event_list.append(event_new)
3018
- event_list = sorted(event_list, key=lambda e: e[1])
3019
-
3020
- tempo = int((60 / 120) * 10 ** 6) # default 120 bpm
3021
- ss = np.empty((0, 2), dtype=np.int16)
3022
- fl = Synth(samplerate=float(sample_rate))
3023
- sfid = fl.sfload(soundfont_path)
3024
- last_t = 0
3025
- for c in range(16):
3026
- fl.program_select(c, sfid, 128 if c == 9 else 0, 0)
3027
- for event in event_list:
3028
- name = event[0]
3029
- sample_len = int(((event[1] / ticks_per_beat) * tempo / (10 ** 6)) * sample_rate)
3030
- sample_len -= int(((last_t / ticks_per_beat) * tempo / (10 ** 6)) * sample_rate)
3031
- last_t = event[1]
3032
- if sample_len > 0:
3033
- sample = fl.get_samples(sample_len).reshape(sample_len, 2)
3034
- ss = np.concatenate([ss, sample])
3035
- if name == "set_tempo":
3036
- tempo = event[2]
3037
- elif name == "patch_change":
3038
- c, p = event[2:4]
3039
- fl.program_select(c, sfid, 128 if c == 9 else 0, p)
3040
- elif name == "control_change":
3041
- c, cc, v = event[2:5]
3042
- fl.cc(c, cc, v)
3043
- elif name == "note_on" and event[3] > 0:
3044
- c, p, v = event[2:5]
3045
- fl.noteon(c, p, v)
3046
- elif name == "note_off" or (name == "note_on" and event[3] == 0):
3047
- c, p = event[2:4]
3048
- fl.noteoff(c, p)
3049
-
3050
- fl.delete()
3051
- if ss.shape[0] > 0:
3052
- max_val = np.abs(ss).max()
3053
- if max_val != 0:
3054
- ss = (ss / max_val) * np.iinfo(np.int16).max
3055
- ss = ss.astype(np.int16)
3056
-
3057
- if trim_silence:
3058
- threshold = np.std(np.abs(ss)) * silence_threshold
3059
- exceeded_thresh = np.abs(ss) > threshold
3060
- if np.any(exceeded_thresh):
3061
- last_idx = np.where(exceeded_thresh)[0][-1]
3062
- ss = ss[:last_idx+1]
3063
-
3064
- if output_for_gradio:
3065
- return ss
3066
 
3067
- ss = ss.swapaxes(1, 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3068
 
3069
- raw_audio = normalize_volume(ss, volume_scale)
 
 
3070
 
3071
- if write_audio_to_WAV:
 
3072
 
3073
- filename = midi_file.split('.')[-2] + '.wav'
 
 
 
 
3074
 
3075
- r_audio = raw_audio.T
 
 
 
 
 
3076
 
3077
- r_audio = np.int16(r_audio / np.max(np.abs(r_audio)) * 32767)
 
 
3078
 
3079
- with wave.open(filename, 'w') as wf:
 
 
 
 
 
 
 
 
 
 
3080
  wf.setframerate(sample_rate)
3081
  wf.setsampwidth(2)
3082
- wf.setnchannels(r_audio.shape[1])
3083
- wf.writeframes(r_audio)
 
 
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 <[email protected]> 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
+ enable_reverb=False,
3242
+ reverb_param_dic={'roomsize': 0,
3243
+ 'damping': 0,
3244
+ 'width': 0,
3245
+ 'level': 0
3246
+ },
3247
+ enable_chorus=False,
3248
+ chorus_param_dic={'nr': 0,
3249
+ 'level': 0,
3250
+ 'speed': 0.1,
3251
+ 'depth': 0,
3252
+ 'type': 0},
3253
+ output_for_gradio=False,
3254
+ write_audio_to_WAV=False,
3255
+ output_WAV_name=''
3256
+ ):
3257
+
3258
+ if midi_opus[1]:
3259
+
3260
+ ticks_per_beat, *tracks = midi_opus
3261
+ if not tracks:
3262
+ return None
3263
+
3264
+ # Flatten & convert delta-times to absolute-time
3265
+ events = []
3266
+ for track in tracks:
3267
+ abs_t = 0
3268
+ for name, dt, *data in track:
3269
+ abs_t += dt
3270
+ events.append([name, abs_t, *data])
3271
+ events.sort(key=lambda e: e[1])
3272
+
3273
+ # Setup FluidSynth
3274
+ fl = Synth(samplerate=float(sample_rate))
3275
+ sfid = fl.sfload(soundfont_path)
3276
+ for chan in range(16):
3277
+ # channel 9 = percussion GM bank 128
3278
+ fl.program_select(chan, sfid, 128 if chan == 9 else 0, 0)
3279
+
3280
+ if enable_reverb:
3281
+ fl.set_reverb(roomsize=reverb_param_dic['roomsize'],
3282
+ damping=reverb_param_dic['damping'],
3283
+ width=reverb_param_dic['width'],
3284
+ level=reverb_param_dic['level']
3285
+ )
3286
+
3287
+ """
3288
+ roomsize Reverb room size value (0.0-1.0)
3289
+ damping Reverb damping value (0.0-1.0)
3290
+ width Reverb width value (0.0-100.0)
3291
+ level Reverb level value (0.0-1.0)
3292
+ """
3293
+
3294
+ if enable_chorus:
3295
+ fl.set_chorus(nr=chorus_param_dic['nr'],
3296
+ level=chorus_param_dic['level'],
3297
+ speed=chorus_param_dic['speed'],
3298
+ depth=chorus_param_dic['depth'],
3299
+ type=chorus_param_dic['type']
3300
+ )
3301
+
3302
+ """
3303
+ nr Chorus voice count (0-99, CPU time consumption proportional to this value)
3304
+ level Chorus level (0.0-10.0)
3305
+ speed Chorus speed in Hz (0.29-5.0)
3306
+ depth_ms Chorus depth (max value depends on synth sample rate, 0.0-21.0 is safe for sample rate values up to 96KHz)
3307
+ type Chorus waveform type (0=sine, 1=triangle)
3308
+ """
3309
+
3310
+ # Playback vars
3311
+ tempo = int((60 / 120) * 1e6) # default 120bpm
3312
+ last_t = 0
3313
+ ss = np.empty((0, 2), dtype=np.int16)
3314
+
3315
+ for name, cur_t, *data in events:
3316
+ # compute how many samples have passed since the last event
3317
+ delta_ticks = cur_t - last_t
3318
+ last_t = cur_t
3319
+ dt_seconds = (delta_ticks / ticks_per_beat) * (tempo / 1e6)
3320
+ sample_len = int(dt_seconds * sample_rate)
3321
+ if sample_len > 0:
3322
+ buf = fl.get_samples(sample_len).reshape(-1, 2)
3323
+ ss = np.concatenate([ss, buf], axis=0)
3324
+
3325
+ # Dispatch every known event
3326
+ if name == "note_on" and data[2] > 0:
3327
+ chan, note, vel = data
3328
+ fl.noteon(chan, note, vel)
3329
+
3330
+ elif name == "note_off" or (name == "note_on" and data[2] == 0):
3331
+ chan, note = data[:2]
3332
+ fl.noteoff(chan, note)
3333
+
3334
+ elif name == "patch_change":
3335
+ chan, patch = data[:2]
3336
+ bank = 128 if chan == 9 else 0
3337
+ fl.program_select(chan, sfid, bank, patch)
3338
+
3339
+ elif name == "control_change":
3340
+ chan, ctrl, val = data[:3]
3341
+ fl.cc(chan, ctrl, val)
3342
+
3343
+ elif name == "key_after_touch":
3344
+ chan, note, vel = data
3345
+ # fl.key_pressure(chan, note, vel)
3346
+
3347
+ elif name == "channel_after_touch":
3348
+ chan, vel = data
3349
+ # fl.channel_pressure(chan, vel)
3350
+
3351
+ elif name == "pitch_wheel_change":
3352
+ chan, wheel = data
3353
+ fl.pitch_bend(chan, wheel)
3354
+
3355
+ elif name == "song_position":
3356
+ # song_pos = data[0]; # often not needed for playback
3357
+ pass
3358
+
3359
+ elif name == "song_select":
3360
+ # song_number = data[0]
3361
+ pass
3362
+
3363
+ elif name == "tune_request":
3364
+ # typically resets tuning; FS handles internally
3365
+ pass
3366
+
3367
+ elif name in ("sysex_f0", "sysex_f7"):
3368
+ raw_bytes = data[0]
3369
+ # fl.sysex(raw_bytes)
3370
+ pass
3371
+
3372
+ # Meta events & others—no direct audio effect, so we skip or log
3373
+ elif name in (
3374
+ "set_tempo", # handled below
3375
+ "end_track",
3376
+ "text_event", "text_event_08", "text_event_09", "text_event_0a",
3377
+ "text_event_0b", "text_event_0c", "text_event_0d", "text_event_0e", "text_event_0f",
3378
+ "copyright_text_event", "track_name", "instrument_name",
3379
+ "lyric", "marker", "cue_point",
3380
+ "smpte_offset", "time_signature", "key_signature",
3381
+ "sequencer_specific", "raw_meta_event"
3382
+ ):
3383
+ if name == "set_tempo":
3384
+ tempo = data[0]
3385
+ # else: skip all other meta & text; you could hook in logging here
3386
+ continue
3387
+
3388
+ else:
3389
+ # unknown event type
3390
+ continue
3391
+
3392
+ # Cleanup synth
3393
+ fl.delete()
3394
+
3395
+ if ss.size:
3396
+ maxv = np.abs(ss).max()
3397
+ if maxv:
3398
+ ss = (ss / maxv) * np.iinfo(np.int16).max
3399
+ ss = ss.astype(np.int16)
3400
+
3401
+ # Optional trimming of trailing silence
3402
+ if trim_silence and ss.size:
3403
+ thresh = np.std(np.abs(ss)) * silence_threshold
3404
+ idx = np.where(np.abs(ss) > thresh)[0]
3405
+ if idx.size:
3406
+ ss = ss[: idx[-1] + 1]
3407
+
3408
+ # For Gradio you might want raw int16 PCM
3409
+ if output_for_gradio:
3410
+ return ss
3411
+
3412
+ # Swap to (channels, samples) and normalize for playback
3413
+ ss = ss.T
3414
+ raw_audio = normalize_audio(ss, target_level_db=volume_level_db)
3415
+
3416
+ # Optionally write WAV to disk
3417
+ if write_audio_to_WAV:
3418
+ wav_name = midi_file.rsplit('.', 1)[0] + '.wav'
3419
+ if output_WAV_name != '':
3420
+ wav_name = output_WAV_name
3421
+ pcm = np.int16(raw_audio.T / np.max(np.abs(raw_audio)) * 32767)
3422
+ with wave.open(wav_name, 'wb') as wf:
3423
+ wf.setframerate(sample_rate)
3424
+ wf.setsampwidth(2)
3425
+ wf.setnchannels(pcm.shape[1])
3426
+ wf.writeframes(pcm.tobytes())
3427
+
3428
+ return raw_audio
3429
 
3430
  else:
3431
  return None
3432
 
3433
+ #===============================================================================
3434
+
3435
+ def midi_to_colab_audio(midi_file,
3436
+ soundfont_path='/usr/share/sounds/sf2/FluidR3_GM.sf2',
3437
+ sample_rate=16000,
3438
+ volume_level_db=-1,
3439
  trim_silence=True,
3440
  silence_threshold=0.1,
3441
+ enable_reverb=False,
3442
+ reverb_param_dic={'roomsize': 0,
3443
+ 'damping': 0,
3444
+ 'width': 0,
3445
+ 'level': 0
3446
+ },
3447
+ enable_chorus=False,
3448
+ chorus_param_dic={'nr': 0,
3449
+ 'level': 0,
3450
+ 'speed': 0.1,
3451
+ 'depth': 0,
3452
+ 'type': 0},
3453
  output_for_gradio=False,
3454
+ write_audio_to_WAV=False,
3455
+ output_WAV_name=''
3456
+ ):
3457
+ """
 
3458
  Returns raw audio to pass to IPython.disaply.Audio func
3459
 
3460
  Example usage:
 
3462
  from IPython.display import Audio
3463
 
3464
  display(Audio(raw_audio, rate=16000, normalize=False))
3465
+ """
 
 
 
 
 
 
 
 
 
 
3466
 
3467
+ # Read and decode MIDI → opus event list
3468
+ ticks_per_beat, *tracks = midi2opus(open(midi_file, 'rb').read())
3469
+ if not tracks:
3470
+ return None
3471
 
3472
+ # Flatten & convert delta-times to absolute-time
3473
+ events = []
3474
+ for track in tracks:
3475
+ abs_t = 0
3476
+ for name, dt, *data in track:
3477
+ abs_t += dt
3478
+ events.append([name, abs_t, *data])
3479
+ events.sort(key=lambda e: e[1])
3480
+
3481
+ # Setup FluidSynth
3482
+ fl = Synth(samplerate=float(sample_rate))
3483
+ sfid = fl.sfload(soundfont_path)
3484
+ for chan in range(16):
3485
+ # channel 9 = percussion GM bank 128
3486
+ fl.program_select(chan, sfid, 128 if chan == 9 else 0, 0)
3487
+
3488
+ if enable_reverb:
3489
+ fl.set_reverb(roomsize=reverb_param_dic['roomsize'],
3490
+ damping=reverb_param_dic['damping'],
3491
+ width=reverb_param_dic['width'],
3492
+ level=reverb_param_dic['level']
3493
+ )
3494
+
3495
+ """
3496
+ roomsize Reverb room size value (0.0-1.0)
3497
+ damping Reverb damping value (0.0-1.0)
3498
+ width Reverb width value (0.0-100.0)
3499
+ level Reverb level value (0.0-1.0)
3500
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3501
 
3502
+ if enable_chorus:
3503
+ fl.set_chorus(nr=chorus_param_dic['nr'],
3504
+ level=chorus_param_dic['level'],
3505
+ speed=chorus_param_dic['speed'],
3506
+ depth=chorus_param_dic['depth'],
3507
+ type=chorus_param_dic['type']
3508
+ )
3509
+
3510
+ """
3511
+ nr Chorus voice count (0-99, CPU time consumption proportional to this value)
3512
+ level Chorus level (0.0-10.0)
3513
+ speed Chorus speed in Hz (0.29-5.0)
3514
+ depth_ms Chorus depth (max value depends on synth sample rate, 0.0-21.0 is safe for sample rate values up to 96KHz)
3515
+ type Chorus waveform type (0=sine, 1=triangle)
3516
+ """
3517
+ # Playback vars
3518
+ tempo = int((60 / 120) * 1e6) # default 120bpm
3519
+ last_t = 0
3520
+ ss = np.empty((0, 2), dtype=np.int16)
3521
+
3522
+ for name, cur_t, *data in events:
3523
+ # compute how many samples have passed since the last event
3524
+ delta_ticks = cur_t - last_t
3525
+ last_t = cur_t
3526
+ dt_seconds = (delta_ticks / ticks_per_beat) * (tempo / 1e6)
3527
+ sample_len = int(dt_seconds * sample_rate)
3528
+ if sample_len > 0:
3529
+ buf = fl.get_samples(sample_len).reshape(-1, 2)
3530
+ ss = np.concatenate([ss, buf], axis=0)
3531
+
3532
+ # Dispatch every known event
3533
+ if name == "note_on" and data[2] > 0:
3534
+ chan, note, vel = data
3535
+ fl.noteon(chan, note, vel)
3536
+
3537
+ elif name == "note_off" or (name == "note_on" and data[2] == 0):
3538
+ chan, note = data[:2]
3539
+ fl.noteoff(chan, note)
3540
+
3541
+ elif name == "patch_change":
3542
+ chan, patch = data[:2]
3543
+ bank = 128 if chan == 9 else 0
3544
+ fl.program_select(chan, sfid, bank, patch)
3545
+
3546
+ elif name == "control_change":
3547
+ chan, ctrl, val = data[:3]
3548
+ fl.cc(chan, ctrl, val)
3549
+
3550
+ elif name == "key_after_touch":
3551
+ chan, note, vel = data
3552
+ # fl.key_pressure(chan, note, vel)
3553
+
3554
+ elif name == "channel_after_touch":
3555
+ chan, vel = data
3556
+ # fl.channel_pressure(chan, vel)
3557
+
3558
+ elif name == "pitch_wheel_change":
3559
+ chan, wheel = data
3560
+ fl.pitch_bend(chan, wheel)
3561
+
3562
+ elif name == "song_position":
3563
+ # song_pos = data[0]; # often not needed for playback
3564
+ pass
3565
+
3566
+ elif name == "song_select":
3567
+ # song_number = data[0]
3568
+ pass
3569
+
3570
+ elif name == "tune_request":
3571
+ # typically resets tuning; FS handles internally
3572
+ pass
3573
+
3574
+ elif name in ("sysex_f0", "sysex_f7"):
3575
+ raw_bytes = data[0]
3576
+ # fl.sysex(raw_bytes)
3577
+ pass
3578
+
3579
+ # Meta events & others—no direct audio effect, so we skip or log
3580
+ elif name in (
3581
+ "set_tempo", # handled below
3582
+ "end_track",
3583
+ "text_event", "text_event_08", "text_event_09", "text_event_0a",
3584
+ "text_event_0b", "text_event_0c", "text_event_0d", "text_event_0e", "text_event_0f",
3585
+ "copyright_text_event", "track_name", "instrument_name",
3586
+ "lyric", "marker", "cue_point",
3587
+ "smpte_offset", "time_signature", "key_signature",
3588
+ "sequencer_specific", "raw_meta_event"
3589
+ ):
3590
+ if name == "set_tempo":
3591
+ tempo = data[0]
3592
+ # else: skip all other meta & text; you could hook in logging here
3593
+ continue
3594
 
3595
+ else:
3596
+ # unknown event type
3597
+ continue
3598
 
3599
+ # Cleanup synth
3600
+ fl.delete()
3601
 
3602
+ if ss.size:
3603
+ maxv = np.abs(ss).max()
3604
+ if maxv:
3605
+ ss = (ss / maxv) * np.iinfo(np.int16).max
3606
+ ss = ss.astype(np.int16)
3607
 
3608
+ # Optional trimming of trailing silence
3609
+ if trim_silence and ss.size:
3610
+ thresh = np.std(np.abs(ss)) * silence_threshold
3611
+ idx = np.where(np.abs(ss) > thresh)[0]
3612
+ if idx.size:
3613
+ ss = ss[: idx[-1] + 1]
3614
 
3615
+ # For Gradio you might want raw int16 PCM
3616
+ if output_for_gradio:
3617
+ return ss
3618
 
3619
+ # Swap to (channels, samples) and normalize for playback
3620
+ ss = ss.T
3621
+ raw_audio = normalize_audio(ss, target_level_db=volume_level_db)
3622
+
3623
+ # Optionally write WAV to disk
3624
+ if write_audio_to_WAV:
3625
+ wav_name = midi_file.rsplit('.', 1)[0] + '.wav'
3626
+ if output_WAV_name != '':
3627
+ wav_name = output_WAV_name
3628
+ pcm = np.int16(raw_audio.T / np.max(np.abs(raw_audio)) * 32767)
3629
+ with wave.open(wav_name, 'wb') as wf:
3630
  wf.setframerate(sample_rate)
3631
  wf.setsampwidth(2)
3632
+ wf.setnchannels(pcm.shape[1])
3633
+ wf.writeframes(pcm.tobytes())
3634
+
3635
+ return raw_audio
3636
 
 
 
 
 
 
3637
  #===================================================================================================================