4
4
Copyright 2012, Willem Vree
5
5
6
6
Synth Class, partially copied from pyFluidSynth, Copyright 2008 Nathan Whitehead
7
+
8
+ Modified February 2024 S.M.Blinkhorn to pass pointer arguments into CDLL as c_void_p types
9
+ thereby avoiding Undefined Behaviours leading to access violations on some OSs,
10
+ notably, 64bit Windows.
7
11
'''
8
12
9
13
import time
10
- from ctypes import c_int , c_uint , c_double , c_char_p , byref , CDLL
14
+ from ctypes import c_int , c_uint , c_double , c_float , c_char_p , c_wchar_p , c_void_p , byref , CDLL
11
15
# from ctypes.util import find_library
12
16
13
17
import sys
@@ -49,6 +53,7 @@ def b(s):
49
53
try :
50
54
lib = lib_locations [i ]
51
55
F = CDLL (lib )
56
+ # print("Library %s" % lib)
52
57
break
53
58
except :
54
59
i += 1
@@ -58,7 +63,11 @@ def b(s):
58
63
59
64
class Synth : # interface for the FluidSynth synthesizer
60
65
def __init__ (self , gain = 0.2 , samplerate = 44100.0 , bsize = 64 , output_path = None ):
66
+ self .handle = c_void_p
67
+ F .new_fluid_settings .restype = self .handle
68
+ F .new_fluid_synth .restype = self .handle
61
69
self .settings = F .new_fluid_settings ()
70
+ # print("Settings: %s" % hex(self.settings))
62
71
self .setting_setnum ('synth.gain' , gain )
63
72
# self.setting_setnum('synth.sample-rate', samplerate)
64
73
# self.setting_setint('audio.period-size', bsize)
@@ -72,28 +81,28 @@ def __init__(self, gain=0.2, samplerate=44100.0, bsize=64, output_path=None):
72
81
self .setting_setstr ("player.timing-source" , "sample" )
73
82
# since this is a non-realtime scenario, there is no need to pin the sample data
74
83
self .setting_setint ("synth.lock-memory" , 0 )
75
- self .synth = F .new_fluid_synth (self .settings )
84
+ self .synth = F .new_fluid_synth (( c_void_p ( self .settings )) )
76
85
self .audio_driver = None
77
86
78
87
def setting_setstr (self , name , value ):
79
- F .fluid_settings_setstr (self .settings , c_char_p (b (name )), c_char_p (b (value )))
88
+ F .fluid_settings_setstr (c_void_p ( self .settings ) , c_char_p (b (name )), c_char_p (b (value )))
80
89
81
90
def setting_setint (self , name , value ):
82
- F .fluid_settings_setint (self .settings , c_char_p (b (name )), c_int (value ))
91
+ F .fluid_settings_setint (c_void_p ( self .settings ) , c_char_p (b (name )), c_int (value ))
83
92
84
93
def setting_setnum (self , name , value ):
85
- F .fluid_settings_setnum (self .settings , c_char_p (b (name )), c_double (value ))
94
+ F .fluid_settings_setnum (c_void_p ( self .settings ) , c_char_p (b (name )), c_double (value ))
86
95
87
96
def setting_getint (self , name ):
88
97
n = c_int ()
89
- F .fluid_settings_getint (self .settings , c_char_p (b (name )), byref (n ))
98
+ F .fluid_settings_getint (c_void_p ( self .settings ) , c_char_p (b (name )), byref (n ))
90
99
return n
91
100
92
101
def start (self , driver = None ): # initialize the audio driver
93
102
if driver is not None :
94
103
assert (driver in ['alsa' , 'oss' , 'jack' , 'portaudio' , 'sndmgr' , 'coreaudio' , 'dsound' , 'pulseaudio' ])
95
104
self .setting_setstr ('audio.driver' , driver )
96
- self .audio_driver = F .new_fluid_audio_driver (self .settings , self .synth )
105
+ self .audio_driver = F .new_fluid_audio_driver (c_void_p ( self .settings ), c_void_p ( self .synth ) )
97
106
if not self .audio_driver : # API returns 0 on error (not None)
98
107
self .audio_driver = None
99
108
# else: # print some info
@@ -103,35 +112,35 @@ def start(self, driver=None): # initialize the audio driver
103
112
104
113
def delete (self ): # release all memory
105
114
if self .audio_driver is not None :
106
- F .delete_fluid_audio_driver (self .audio_driver )
107
- F .delete_fluid_synth (self .synth )
108
- F .delete_fluid_settings (self .settings )
115
+ F .delete_fluid_audio_driver (c_void_p ( self .audio_driver ) )
116
+ F .delete_fluid_synth (c_void_p ( self .synth ) )
117
+ F .delete_fluid_settings (c_void_p ( self .settings ) )
109
118
self .settings = self .synth = self .audio_driver = None
110
119
111
120
def sfload (self , filename , update_midi_preset = 0 ): # load soundfont
112
- return F .fluid_synth_sfload (self .synth , c_char_p (b (filename )), update_midi_preset )
121
+ return F .fluid_synth_sfload (c_void_p ( self .synth ) , c_char_p (b (filename )), update_midi_preset )
113
122
114
123
def sfunload (self , sfid , update_midi_preset = 0 ): # clear soundfont
115
- return F .fluid_synth_sfunload (self .synth , sfid , update_midi_preset )
124
+ return F .fluid_synth_sfunload (c_void_p ( self .synth ) , sfid , update_midi_preset )
116
125
117
126
def program_select (self , chan , sfid , bank , preset ):
118
- return F .fluid_synth_program_select (self .synth , chan , sfid , bank , preset )
127
+ return F .fluid_synth_program_select (c_void_p ( self .synth ) , chan , sfid , bank , preset )
119
128
120
129
def set_reverb (self , roomsize , damping , width , level ): # change reverb model parameters
121
- return F .fluid_synth_set_reverb (self .synth , c_double (roomsize ), c_double (damping ), c_double (width ), c_double (level ))
130
+ return F .fluid_synth_set_reverb (c_void_p ( self .synth ) , c_double (roomsize ), c_double (damping ), c_double (width ), c_double (level ))
122
131
123
132
def set_chorus (self , nr , level , speed , depth_ms , type ): # change chorus model pararmeters
124
- return F .fluid_synth_set_chorus (self .synth , nr , c_double (level ), c_double (speed ), c_double (depth_ms ), type )
133
+ return F .fluid_synth_set_chorus (c_void_p ( self .synth ) , nr , c_double (level ), c_double (speed ), c_double (depth_ms ), type )
125
134
126
135
def set_reverb_level (self , level ): # set the amount of reverb (0-127) on all midi channels
127
136
n = F .fluid_synth_count_midi_channels (self .synth )
128
137
for chan in range (n ):
129
- F .fluid_synth_cc (self .synth , chan , 91 , level ) # midi control change #91 == reverb level
138
+ F .fluid_synth_cc (c_void_p ( self .synth ) , chan , 91 , level ) # midi control change #91 == reverb level
130
139
131
140
def set_chorus_level (self , level ): # set the amount of chorus (0-127) on all midi channels
132
141
n = F .fluid_synth_count_midi_channels (self .synth )
133
142
for chan in range (n ):
134
- F .fluid_synth_cc (self .synth , chan , 93 , level ) # midi control change #93 == chorus level
143
+ F .fluid_synth_cc (c_void_p ( self .synth ) , chan , 93 , level ) # midi control change #93 == chorus level
135
144
136
145
def set_gain (self , gain ):
137
146
self .setting_setnum ('synth.gain' , gain )
@@ -148,38 +157,40 @@ class Player: # interface for the FluidSynth internal midi player
148
157
LOOP_INFINITELY = - 1
149
158
150
159
def __init__ (self , flsynth ):
160
+ self .handle = c_void_p
161
+ F .new_fluid_player .restype = self .handle
151
162
self .flsynth = flsynth # an instance of class Synth
152
- self .player = F .new_fluid_player (self .flsynth .synth )
163
+ self .player = F .new_fluid_player (c_void_p ( self .flsynth .synth ) )
153
164
154
165
def add (self , midifile ): # add midifile to the playlist
155
- return F .fluid_player_add (self .player , c_char_p (b (midifile ))) == 0
166
+ return F .fluid_player_add (c_void_p ( self .player ) , c_char_p (b (midifile ))) == 0
156
167
157
168
def play (self , offset = 0 ): # start playing at time == offset in midi ticks
158
169
self .seek (offset )
159
- F .fluid_player_play (self .player )
170
+ F .fluid_player_play (c_void_p ( self .player ) )
160
171
161
172
def set_loop (self , loops = LOOP_INFINITELY ):
162
- F .fluid_player_set_loop (self .player , loops )
173
+ F .fluid_player_set_loop (c_void_p ( self .player ) , loops )
163
174
164
175
def stop (self ): # stop playing and return position in midi ticks
165
- F .fluid_player_stop (self .player )
166
- F .fluid_synth_all_notes_off (self .flsynth .synth , - 1 ) # -1 == all channels
176
+ F .fluid_player_stop (c_void_p ( self .player ) )
177
+ F .fluid_synth_all_notes_off (c_void_p ( self .flsynth .synth ) , - 1 ) # -1 == all channels
167
178
return self .get_ticks ()
168
179
169
180
def wait (self ): # wait until player is finished
170
- F .fluid_player_join (self .player )
181
+ F .fluid_player_join (c_void_p ( self .player ) )
171
182
172
183
def get_status (self ): # 1 == playing, 2 == player finished
173
- return F .fluid_player_get_status (self .player )
184
+ return F .fluid_player_get_status (c_void_p ( self .player ) )
174
185
175
186
def get_ticks (self ): # get current position in midi ticks
176
187
# oldFluid # t = F.fluid_player_get_ticks(self.player)
177
- t = F .fluid_player_get_current_tick (self .player )
188
+ t = F .fluid_player_get_current_tick (c_void_p ( self .player ) )
178
189
return t
179
190
180
191
def seek (self , ticks_p ): # go to position ticks_p (in midi ticks)
181
- F .fluid_synth_all_notes_off (self .flsynth .synth , - 1 ) # -1 == all channels
182
- ticks = F .fluid_player_seek (self .player , ticks_p )
192
+ F .fluid_synth_all_notes_off (c_void_p ( self .flsynth .synth ) , - 1 ) # -1 == all channels
193
+ ticks = F .fluid_player_seek (c_void_p ( self .player ) , ticks_p )
183
194
return ticks
184
195
185
196
def seekW (self , ticks_p ): # go to position ticks_p (in midi ticks) and wait until seeked
@@ -192,27 +203,29 @@ def seekW(self, ticks_p): # go to position ticks_p (in midi ticks) and wait unti
192
203
return ticks
193
204
194
205
def get_length (self ): # get duration of a midi track in ticks
195
- return F .fluid_player_get_total_ticks (self .player )
206
+ return F .fluid_player_get_total_ticks (c_void_p ( self .player ) )
196
207
197
208
def delete (self ):
198
- F .delete_fluid_player (self .player )
209
+ F .delete_fluid_player (c_void_p ( self .player ) )
199
210
200
211
def renderLoop (self , callback = None ): # render midi file to audio file
201
- renderer = F .new_fluid_file_renderer (self .flsynth .synth )
212
+ self .handle = c_void_p
213
+ F .new_fluid_file_renderer .restype = self .handle
214
+ renderer = F .new_fluid_file_renderer (c_void_p (self .flsynth .synth ))
202
215
if not renderer :
203
216
print ('failed to create file renderer' )
204
217
return
205
218
k = self .flsynth .setting_getint ('audio.period-size' ) # get block size (samples are rendered one block at a time)
206
219
samples = 0 # sample counter
207
220
while self .get_status () == 1 :
208
- if F .fluid_file_renderer_process_block (renderer ) != 0 : # render one block
221
+ if F .fluid_file_renderer_process_block (c_void_p ( renderer ) ) != 0 : # render one block
209
222
print ('renderer_loop error' )
210
223
break
211
224
samples += k .value # increment with block size
212
225
if callback : callback (samples ) # for progress reporting
213
226
self .stop () # just for sure: stop the playback explicitly and wait until finished
214
- F .fluid_player_join (self .player )
215
- F .delete_fluid_file_renderer (renderer )
227
+ F .fluid_player_join (c_void_p ( self .player ) )
228
+ F .delete_fluid_file_renderer (c_void_p ( renderer ) )
216
229
return samples
217
230
218
231
def set_render_mode (self , file_name , file_type ): # set audio file and audio type
0 commit comments