-
Notifications
You must be signed in to change notification settings - Fork 263
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
Prevent loopstart mangling by fixup_sample if equal to sample start #184
Conversation
…ot if they are equal. Signed-off-by: Marcus Weseloh <marcus@weseloh.cc>
I dont even understand why we checked for I think the user should be informed in case of invalid loops, as they can cause some real trouble as we just saw. However as I just tested this is would be very spammy. Might only become a debug message... will think about it, have some tests and merge it later. |
Yes, having more debug messages if loop points are being changed by FS would be great. I get the impression that there are quite a few problems with weird soundfonts that could benefit if we could just tell people to switch on debug messages and send the output. |
Actually all messages of any level are printed out (regardless of the |
Ah, good point. Yes, I agree that it should be FLUID_INFO. And it would be more in line with other programs to only print INFO if --verbose is specified. |
Decided to inform the user once with a summarizing |
Sounds good. Although the "extending loop to full sample" is not technically correct. The code tries to offset loopstart and loopend by 8 if the sample is long enough, otherwise by 1. I do wonder why the code does this. Why not simply set loopstart to sam->start and loopend to sam->end? |
Well ok, I call that "full" sample, hoping that noone can actually hear the difference :)
Because Emu Systems thought this would be a good idea:
|
Ah, I see, thanks! But the description is a little bit unclear. Especially with regard to the "four on each side" part. Is it four data points on each side of the loop, or four on each side of the two loop points? I guess it's the former. Then fixup_sample should really offset by 4, not by 8, shouldn't it? This might seem overly picky... but as we've seen in #171, even such a small offset can make a huge difference to perceived pitch if the sample is extremely short and only consists of a single wave form period. |
It is indeed. I understand it as the latter one, so I guess the implementation is correct. |
Ahh, there are more details further down in the spec:
According to this, the samples in the soundfont in #171 are actually invalid. And the fix in this pull request, allowing sam->loopstart to be equal to sam->start, is against the spec. But I guess fixup_sample is all about supporting broken soundfonts in the first place and fix them so that SF can play them correctly. As we've seen, having start = loopstart (and I guess also end = loopend) is not a problem for SF, at least with the default interpolation method. Would probably be good to check if the other interpolation methods can also cope with this change. |
Investigating #149. It is so headache inducing.
But previously it was also against spec because we didnt necessarily had these 8 padding points right?
Yep, I'll have another disimprovement commit ready.
Agreed, though I cant imagine any problem this might cause... |
in order to stay in sync with FluidSynth#184
Yes, of course. And I think fixup_sample is not about making samples to conform with the specs, but to take really broken samples and change them so that they can be played like they were probably intended to. And as FS doesn't seem to need the 8 point padding, maybe it's best to really only do the minimal amount of mangling. So only fix loop points that are outside of the sample and leave the rest as is? |
And completely forgetting about
and only using what's now in the else branch? Ok, I would be fine with that. Esp. because padding only make sense if the surrounding data is identical, as pointed out by spec, to help interpolation algorithms. But we dont make sure this is the case. We dont even check for it. So yeah, I think let's just use the logic of the else branch. |
Yes, but maybe go even further than that. The +1 / -1 offset might not be necessary either. After all, the loops we saw in #171 had loopstart = sam->start and worked fine. And considering the comment by Element in #149:
Maybe just do that? Only clamp sam->loopstart to sam->start? And leave the rest alone? |
But I'm afraid if sam.loopend really exceeds sam.end we may get a buffer overrun somewhere. |
Yes, but to me it's not clear what reservations element had in this case:
|
Looking at the code again, I think we should check sam->loopend against sdtachunk_size, just like sam->end. If I understand correctly, then sdtachunk_size is the actual length of the sample data, including the zero-padding data that the SF format specifies. So if we just make sure that start and loopstart are > 0, end and loopend are < sdtachunk_size, then there's no risk of buffer overruns. |
inv_start = (sam->loopstart < sam->start) || (sam->loopstart >= sam->loopend); | ||
// while loop end is the first point AFTER the last sample of the loop. | ||
/* while loop end is the first point AFTER the last sample of the loop */ | ||
inv_end = (sam->loopend > sam->end+1) || (sam->loopstart >= sam->loopend); |
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.
sam->end is should be the position AFTER the last sample data, pointing to the first zero-byte after valid sample data. So no need for the +1 here
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.
Digging in the code I agree. sam.end comes directly from the sdhr chunk which specifies it after valid sample data and we dont seem to correct it by -1. But then fluidsynths documentation is incorrect.
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 wait, fixup_sample() acutally corrects it by -1 later on. But talking about this particular line of code it still points behind sample data. Insane
But is loopend really allowed to point to the padded area, i.e. after the end of valid sample data? I'm feeling very uncomfortable doing so. if there is any garbage in that padded area we get undefined output. |
I must admit I don't like this whole "silently changing looping points, trying to make the best of it" thing at all. We don't even tell people that their font is broken. So when it sounds horrible, FluidSynth is to blame. If were just to reject the broken data and loudly complain about it, then maybe people would try to fix their fonts or font editors... But maybe that is just wishful thinking.
Well, if there is garbage and not the zero bytes as the spec requires, we'll get either horrible screeching noises... or pink or white noise in the best case. But that would only happen if the SoundFont editor that wrote the file is really broken. From what I've seen so far, most problems were just off-by-one errors. This whole fixup_sample function is about making broken soundfonts that don't adhere to the spec play (mostly) without problems. I guess it comes down to what level of "brokenness" we want to cater for and at which point do we simply reject a sample. For example, as I see it, the broken "Final Fantasy" font in #149 has wrong sample->end(!) pointers. They point to the last word of valid data, not to the first zero word in the padded area. The loopend pointer points to end + 1, so at the place where the end pointer should be. According to spec, loopend should be at least end - 7, but as we've learned, that is not really important for FS. So loading those samples without touching any of those broken pointers would make the font sound as intended by the designer. But if we check for loopend > end and adjust the loopend or reject this sample, that font won't work (properly) in FS. Even the 1 word offset of moving the loopend to sample end would make the Guitar sample sound weird, because the loop in that sample is so short. Same with the ocarina. Sorry... lots of text... But if you ask me: I would rather have a loud screeching noise if a sample is broken than a fairly short or quiet clicking artefact or slightly out of tune sound. |
Ok, so you share elements opinion of mangling as little as necessary with loop points. I'm fine with that. Hopefully this commit takes everything into account what we've discussed so far. |
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.
That looks great! Just two small comments about +1 / -1 offsets. Would be great to have another set of eyes on this, though. Preferably Element :-)
{ | ||
FLUID_LOG (FLUID_DBG, _("Sample '%s' has invalid loop start '%d'," | ||
FLUID_LOG (FLUID_DBG, _("Sample '%s' has unusable loop start '%d'," | ||
" setting to sample start at '%d'+1"), sam->name, sam->loopstart, sam->start); | ||
sam->loopstart = sam->start + 1; |
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 the +1? Seems like an arbitrary offset...
{ | ||
FLUID_LOG (FLUID_DBG, _("Sample '%s' has invalid loop stop '%d'," | ||
FLUID_LOG (FLUID_DBG, _("Sample '%s' has out-of-range loop stop '%d'," | ||
" setting to sample stop at '%d'-1"), sam->name, sam->loopend, sam->end); | ||
/* since sam->end points after valid sample data, set loopend to last sample available */ | ||
sam->loopend = sam->end - 1; |
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.
loopend has the same semantic as end, I think. so setting it to end-1 means that the loop reaches end-2 and then jumps to loopstart. I think it should be sam->loopend = sam->end here.
@Element-Green @elementgreen This PR made some (hopefully useful) improvements to fixup_sample(). Could you tell us why you've chosen those +1 / -1 offsets. This would cause the loop to play from
...and perhaps review the changes? |
loopend might not necessarily be out of range
Merging now. I want to glue it together with #183 and test. @elementgreen is informed and will hopefully comment on this when he has time. |
Stumbled over an issue. Soundfonts that dont use sample loops at all may set
and
Meaning the logic we have in this PR is correct, even if the sample loop is ultimately not used, do you agree @mawe42? |
Sorry for the delay in replying to this. Some of the more recent changes I did to the Swami code base involved issues around loops. The SoundFont loading logic is something I originally contributed a long while back, so it had some things in common with the FluidSynth code base. Here is a link to the relevant commit to Swami: In summary: The check for a valid loop should therefore occur in FluidSynth when calculating instrument zones. |
Pretty much what this PR attempts. Additionally it assures that loopend is within the sdta chunk.
Looking at the spec this seems to be something that Emu didnt take into account.
Ok, for now I'm not feeling that familiar with the codebase to actually introduce such a change. Issue #188 created. Anyway, thanks so far. I'll see if it makes sense to merge some of your changes into fixup_sample(). |
fixup_sample in defsloader attempts to repair certain broken sample loop points. But it also mangles the
pointers if sample start and loop start are equal, which leads to weird behavior observed in #171. After applying this fix, the pitch seems to be correct again.
Signed-off-by: Marcus Weseloh marcus@weseloh.cc