Skip to content

Commit 670dd54

Browse files
committed
Fluidsynth fixes #81
Fluidsynth fixes (thanks Mark) Should resolve #81
1 parent 0aefdb0 commit 670dd54

File tree

2 files changed

+48
-35
lines changed

2 files changed

+48
-35
lines changed

CHANGES

+1-1
Original file line numberDiff line numberDiff line change
@@ -628,4 +628,4 @@ different panes. Tie/untie option added for notes. Broken rhythm
628628
menu version. For Linux (Gnome) users these menus are still empty but that is caused by Gnome, not EasyABC.
629629
- Fixed: Playing selection gave an error: AttributeError: 'bytes' object has no attribute 'encode' (https://github.com/jwdj/EasyABC/issues/69)
630630
- Fixed: Now automatic insertion of bars (|) only when no text is selected
631-
631+
- Fixed: FluidSynth issues on 64 bit Mac (thanks Mark)

fluidsynth.py

+47-34
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
Copyright 2012, Willem Vree
55
66
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.
711
'''
812

913
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
1115
# from ctypes.util import find_library
1216

1317
import sys
@@ -49,6 +53,7 @@ def b(s):
4953
try:
5054
lib = lib_locations[i]
5155
F = CDLL(lib)
56+
# print("Library %s" % lib)
5257
break
5358
except:
5459
i += 1
@@ -58,7 +63,11 @@ def b(s):
5863

5964
class Synth: # interface for the FluidSynth synthesizer
6065
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
6169
self.settings = F.new_fluid_settings()
70+
# print("Settings: %s" % hex(self.settings))
6271
self.setting_setnum('synth.gain', gain)
6372
# self.setting_setnum('synth.sample-rate', samplerate)
6473
# 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):
7281
self.setting_setstr("player.timing-source", "sample")
7382
# since this is a non-realtime scenario, there is no need to pin the sample data
7483
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)))
7685
self.audio_driver = None
7786

7887
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)))
8089

8190
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))
8392

8493
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))
8695

8796
def setting_getint(self, name):
8897
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))
9099
return n
91100

92101
def start(self, driver=None): # initialize the audio driver
93102
if driver is not None:
94103
assert(driver in ['alsa', 'oss', 'jack', 'portaudio', 'sndmgr', 'coreaudio', 'dsound', 'pulseaudio'])
95104
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))
97106
if not self.audio_driver: # API returns 0 on error (not None)
98107
self.audio_driver = None
99108
# else: # print some info
@@ -103,35 +112,35 @@ def start(self, driver=None): # initialize the audio driver
103112

104113
def delete(self): # release all memory
105114
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))
109118
self.settings = self.synth = self.audio_driver = None
110119

111120
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)
113122

114123
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)
116125

117126
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)
119128

120129
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))
122131

123132
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)
125134

126135
def set_reverb_level(self, level): # set the amount of reverb (0-127) on all midi channels
127136
n = F.fluid_synth_count_midi_channels(self.synth)
128137
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
130139

131140
def set_chorus_level(self, level): # set the amount of chorus (0-127) on all midi channels
132141
n = F.fluid_synth_count_midi_channels(self.synth)
133142
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
135144

136145
def set_gain(self, gain):
137146
self.setting_setnum('synth.gain', gain)
@@ -148,38 +157,40 @@ class Player: # interface for the FluidSynth internal midi player
148157
LOOP_INFINITELY = -1
149158

150159
def __init__(self, flsynth):
160+
self.handle = c_void_p
161+
F.new_fluid_player.restype = self.handle
151162
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))
153164

154165
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
156167

157168
def play(self, offset=0): # start playing at time == offset in midi ticks
158169
self.seek(offset)
159-
F.fluid_player_play(self.player)
170+
F.fluid_player_play(c_void_p(self.player))
160171

161172
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)
163174

164175
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
167178
return self.get_ticks()
168179

169180
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))
171182

172183
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))
174185

175186
def get_ticks(self): # get current position in midi ticks
176187
# 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))
178189
return t
179190

180191
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)
183194
return ticks
184195

185196
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
192203
return ticks
193204

194205
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))
196207

197208
def delete(self):
198-
F.delete_fluid_player(self.player)
209+
F.delete_fluid_player(c_void_p(self.player))
199210

200211
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))
202215
if not renderer:
203216
print('failed to create file renderer')
204217
return
205218
k = self.flsynth.setting_getint('audio.period-size') # get block size (samples are rendered one block at a time)
206219
samples = 0 # sample counter
207220
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
209222
print('renderer_loop error')
210223
break
211224
samples += k.value # increment with block size
212225
if callback: callback(samples) # for progress reporting
213226
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))
216229
return samples
217230

218231
def set_render_mode(self, file_name, file_type): # set audio file and audio type

0 commit comments

Comments
 (0)