New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: SoundFont Loading Refactor #360
Conversation
| FLUID_LOG(FLUID_DBG, "Sample '%s': invalid loop start '%d', setting to sample start '%d'", | ||
| sample->name, sample->loopstart, sample->start); | ||
| sample->loopstart = sample->start; | ||
| modified = TRUE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (and below) actually introduces a regression for #171. As the comment points out we should not modify this in case of (sample->loopstart < sample->start) || (sample->loopend < sample->start), at least for SF2 files. This handling would be probably ok for SF3, as it does not share a big sample buffer like SF2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it introduces a regression. It's not quite what the comment says, but that's how it is implemented in master:
invalid_loopstart = (sam->loopstart < sam->start) || (sam->loopstart >= sam->loopend);
And when invalid_loopstart is set, loopstart is set to sample->start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sry, my bad. I was convinced it sounded wrong during a first rehearsal. But you're right, it's fine.
src/sfloader/fluid_sfont.h
Outdated
| @@ -24,6 +24,10 @@ | |||
|
|
|||
| #include "fluidsynth.h" | |||
|
|
|||
| FLUIDSYNTH_API int fluid_sample_validate(fluid_sample_t *sample, unsigned int max_end); | |||
| FLUIDSYNTH_API int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int max_end); | |||
| FLUIDSYNTH_API int fluid_sample_decompress_vorbis(fluid_sample_t *sample); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you making these public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point. That's a mistake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do the changes I've made so far look good, or do you think I'm going into the wrong direction?
Overall this looks good to me. It splits the huge overloaded fluid_defsfont.c into more handy parts. It might be worth bringing the lazy-loading feature to the mailing list for discussion, but that's a different story.
Also, I've got one specific question: currently, whenever FluidSynth core requests a preset from a defsfont loader, a new fluid_preset_t struct is allocated... every time. To me that seems totally pointless, because that fluid_preset_t struct only contains pointers to the accessor functions and uses the (singleton) fluid_defpreset_t to do any real work anyway. So I'm thinking of changing the defsfont loader to pre-create those fluid_preset_t structs and only return a pointer to them when asked. That would mean we could get rid of the preset_stack dance in defspreset.
Yes, that TODO has been on the list for long. Your proposal sounds good to me. Not quite sure about the schedule of your works. Would it be #360 -> Re-Formatting -> Preset-Stack-Refactor -> Lazy-Loading ? I'm open to postpone the Reformatting even further, just let me know.
Some notes below.
src/sfloader/fluid_samplecache.c
Outdated
| entry = new_samplecache_entry(sf, sample_start, sample_count); | ||
| if (entry == NULL) | ||
| { | ||
| return FLUID_FAILED; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return without mutex unlock
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well spotted, thanks.
src/sfloader/fluid_samplecache.c
Outdated
| entry->mlocked = (fluid_mlock(entry->sample_data, entry->sample_count * 2) == 0); | ||
| if (entry->sample_data24 != NULL) | ||
| { | ||
| entry->mlocked &= (fluid_mlock(entry->sample_data24, entry->sample_count) == 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose: The first mlock succeeds, the second one fails, so that entry->mlocked is false, causing the memory not to be unlocked in fluid_samplecache_unload().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! Will change it so that it only tries to the mlock 24-bit data if the first mlock succeeds.
src/sfloader/fluid_samplecache.c
Outdated
| { | ||
| #if defined(WIN32) || defined(__OS2__) | ||
| *modification_time = 0; | ||
| return FLUID_OK; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// FIXME: use g_file_info_get_modification_time()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a quickly smashed together C99 version that should retrieve the mod time of a file. Uncompiled and untested, as I'm currently not at home.
int res = FLUID_FAILED;
#if GLIB_CHECK_VERSION(2,26,0) // g_date_time_new_from_timeval_utc() requires at least 2.26
GFile* gfile = g_file_new_for_path (filename);
if(gfile != NULL)
{
GFileInfo* info = g_file_query_info (gfile,
G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_TIME_MODIFIED,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
if (info != NULL)
{
GTimeVal tv;
g_file_info_get_modification_time (info, &tv);
GDateTime* dt = g_date_time_new_from_timeval_utc(&tv);
if(dt != NULL)
{
// TODO: converting int64 to time_t, does it compile? is it safe??
*modification_time = g_date_time_to_unix(dt);
g_date_time_unref(dt);
res = FLUID_OK;
}
g_object_unref(info);
}
g_object_unref(gfile);
}
#else
#warning "Consider upgrading glib to at least version 2.26"
#endif
return res;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks! Regarding the g_date_time_new_from_timeval_utc: we could switch to using a uint64 for the modification_time in fluid_samplecache_t. Then we could just to the following:
*modification_time = tv.tv_sec * 1000000 + tv.tv_usec;
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok sure. But unless you really need microsecond precision for a file mod date, *modification_time = tv.tv_sec should be sufficient (which should result in quite the same behaviour as g_date_time_to_unix()).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, microsecond precision is probably not required. So lets go with seconds and see how it goes. Precision can always be easily increased if it poses a problem...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that the functions you used here are from GIO, not GLIB. I don't think adding another dependency for the modification time is worth it. But we could simply use g_stat:
--- fluid_sys.h:
#include <glib/gstdio.h>
typedef GStatBuf fluid_stat_buf_t;
#define fluid_stat(_filename, _statbuf) g_stat((_filename), (_statbuf))
--- fluid_samplecache.c:
static int fluid_get_file_modification_time(char *filename, time_t *modification_time)
{
fluid_stat_buf_t buf;
if (fluid_stat(filename, &buf))
{
return FLUID_FAILED;
}
*modification_time = buf.st_mtime;
return FLUID_OK;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I didnt notice I was digging in GIO. You're right, and g_stat is the much more elegant way. Thx!
src/sfloader/fluid_sffile.c
Outdated
| /* Load 16-bit sample data */ | ||
| if (sf->fcbs->fseek(sf->sffd, sf->samplepos + (start * 2), SEEK_SET) == FLUID_FAILED) | ||
| { | ||
| perror("error"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perror seems redundant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
src/sfloader/fluid_sffile.c
Outdated
| goto error_exit; | ||
| } | ||
|
|
||
| loaded_data = (short *)FLUID_MALLOC(count * 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a friend of casting malloc() to the destination pointer type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll use FLUID_ARRAY.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm... seeing that FLUID_ARRAY does exactly that (just hidden behind a macro)... what exactly do you propose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, now I understand. You just want to remove the explicit cast?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You just want to remove the explicit cast?
Yes. But FLUID_ARRAY really seems to be the better solution for this purpose (just forgot about it).
seeing that FLUID_ARRAY does exactly that (just hidden behind a macro)...
At least the cast is "abstracted" through the macro, so I dont further care.
src/sfloader/fluid_sffile.c
Outdated
| goto error_exit; | ||
| } | ||
|
|
||
| if (sf->sample24pos && ((start > sf->sample24size) || (end > sf->sample24size))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would postpone this check to the if (sf->sample24pos){...} below. And instead of hard failing in case there is an error related to SM24, I would prefer to just ignore the SM24 then instead of fatally failing to load the whole sample.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good idea. And more in line with how we treat 24-bit samples everywhere else.
src/sfloader/fluid_sffile.c
Outdated
| if (loaded_data24 == NULL) | ||
| { | ||
| FLUID_LOG(FLUID_ERR, "Out of memory"); | ||
| return FLUID_FAILED; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
goto error_exit; instead of direct return to avoid memory leak of loaded_data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
src/sfloader/fluid_sffile.h
Outdated
|
|
||
| struct _SFInst | ||
| { /* Instrument structure */ | ||
| unsigned short idx; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps better use unsigned int for this index?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes... no idea why I chose short here...
src/sfloader/fluid_sfont.h
Outdated
| @@ -170,6 +174,7 @@ struct _fluid_preset_t { | |||
| */ | |||
| struct _fluid_sample_t | |||
| { | |||
| unsigned short idx; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint idx; ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes!
Great, thanks for your feedback and the initial review! I'll carry on then :-) There are a few more refactorings I'd like to do to the remaining defsfont code before attempting to add the new feature.
Ok, I will do that.
It would be great if we could postpone the reformatting until after the additional refactorings I'd like to do. So #360 -> Preset-Stack + a few other refactorings -> Re-Formatting -> Lazy Loading. |
|
I like your idea of only lazy loading sample data first. So I'm going to rebase this branch again, trying to remove most of the functional changes I made to the low-level loading in fluid_sffile that were in preparation for the lazy-loading of structural information. Should have kept this as a clean refactor in the first place and not mix it with functional changes... :-/ Then I will add the new modification time code and get rid of the preset stack and close this branch. Lazy loading will come in a new branch (and possibly after reformatting). Does that sound good to you, or would you rather have the preset stack stuff in a separate PR? |
Ok. But generally I like the idea of having this fluid_sffile to handle the low-level loading. If it's not too much trouble for you I would choose to have the preset stack separately. Apart from that this seems fine to me. |
# Conflicts: # src/sfloader/fluid_sf2.c
# Conflicts: # src/sfloader/fluid_sf2.h
Makes the whole file easier to read and gives the implementation a little more "object-oriented" feel.
They were only used in soundfont loading code, so are probably a remnant from Smurf. The rest of the FluidSynth code doesn't use underscore functions, so remove them here as well for consistency. Also use single quotes in double quoted string, to remove the need for escaping chars.
As fluid_defsfont contains code that deals with fluid_sfont_t and fluid_defsfont_t, fluid_preset_t and fluid_defpreset_t, it sometimes gets very confusion to know which type is currently being accessed by "sfont" or "preset". Also clarify some preset zone / inst zone ambiguities.
8d0a5d2
to
f8bc376
Compare
|
Ok, I've taken out all functional changes to the fluid_sffile code and rebased the branch. Apart from any bugfixes, I would call this refactor finished. The preset_stack removal will come in a new PR. And I don't really mind if you want to do the reformat before or after. |
|
Ok will have a look at it. And just forget about the reformat at least for now, currently too much activity. Will see when it fits. |
|
Ah, g_stat requires at least glib 2.6. Will add a check and warning, just like in your suggested implementation. |
|
No, fluidsynth requires 2.6.5 anyway: Line 384 in 9196636
|
Perfect. |
src/sfloader/fluid_samplecache.c
Outdated
| } | ||
| } | ||
|
|
||
| fluid_list_remove(samplecache_list, entry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be samplecache_list = fluid_list_remove(samplecache_list, entry);. Otherwise samplecache_list is not set to NULL when the last element is removed resulting in a NULL deref in line 248.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, well spotted.
src/sfloader/fluid_sfont.c
Outdated
| return FLUID_FAILED; | ||
| } | ||
|
|
||
| return TRUE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because you're using FLUID_FAILED, this should be FLUID_OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
|
So far testing worked as expected, no problems nor crashes. I also think it's time to add some unittest(s). I added one for the sample cache (#361), mainly aiming to test the ref count mechanism, i.e. to avoid using sample data from a soundfont that has already been unloaded by its parent synth. There is a - fluid_synth_sfont_unref (synth, sfont_info->sfont);
+ fluid_synth_sfont_unref (synth, sfont_info);in fluid_synth.c???) Long story short: This PR is fine. If you want you can take care of those two minor issues right here or I'll have a try. |
|
Yes, that _fluid_sfont_info_t does look like it could be merged into fluid_sfont_t. And the hash does look redundant. But I wonder what Element intended here... why implement this indirection? Maybe he didn't want to change fluid_sfont_t because it was public. And the hashtable to avoid having to iterate a list when searching for the fluid_sfont_info_t for a sfont? If you want to have a go a those two, I would be glad. I'll do the preset_stack removal first, then prepare a lazy loading branch, just to flesh out a few ideas. So I'll merge this PR, ok? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
Just my thought. |
|
Just found that the unit test segfaulted with the vintagedreams sfont using your changes. Fixed a misplaced |
|
Ah, thanks for catching that! |
|
I've just removed the noise debug messages for samples with loopstart = loopend: fa0d103 |
Important: This isn't a finished pull request, but a work in progress. So please do not merge this in it's current form.
I've started this large refactor because I wanted to add a new feature to the soundfont loader: lazy-loading of presets and samples. The idea is that in certain real-time performance situations, small delays in switching presets for loading the required samples are absolutely acceptable. So on loading a new soundfont and selecting a preset, only the absolute minimum of information is actually read from the file and loaded to RAM. This would greatly reduce loading time and save huge amounts of memory for larger soundfonts that contain many instruments and presets. Please note: this feature hasn't been implemented in this PR. I'm just explaining this here to give some background to some of the changes I've made to the loading infrastructure.
Just a quick summary of what has already been done:
and code for both compressed and uncompressed samples
Next I am going to work in the preset loading functions. But before I carry on and add more hours and work, I would really like to get some feedback on the direction I'm taking. Do the changes I've made so far look good, or do you think I'm going into the wrong direction?
Also, I've got one specific question: currently, whenever FluidSynth core requests a preset from a defsfont loader, a new fluid_preset_t struct is allocated... every time. To me that seems totally pointless, because that fluid_preset_t struct only contains pointers to the accessor functions and uses the (singleton) fluid_defpreset_t to do any real work anyway. So I'm thinking of changing the defsfont loader to pre-create those fluid_preset_t structs and only return a pointer to them when asked. That would mean we could get rid of the preset_stack dance in defspreset.