Slightly OT: Parsing SF2; duplicate pbag entries?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

Slightly OT: Parsing SF2; duplicate pbag entries?

Mark Raynsford
Hello.

Apologies for the slightly off-topic question, but I can't think of a
better group to consult when it comes to SoundFont questions.

I'm currently working on some Java code to parse SoundFonts. While I've
completed the actual parsing in the sense that I can extract all of the
data from a given SF2 file, I'm struggling to interpret some of that
data. Specifically, I'm seeing pbag entries in valid and playable
SoundFonts that don't make any sense to me, and I can't seem to find
an explanation for them in the spec.

Here's an example of parsing one such soundfont
(http://ataxia.io7m.com/2019/02/22/basic.sf2):

TRACE com.io7m.jnoisetype.vanilla.NTParsers: [phdr][0] NTParsedPreset{name=NTPresetName{value=p0}, preset=0, bank=0, presetBagIndex=0, library=0, genre=0, morphology=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [phdr][1] NTParsedPreset{name=NTPresetName{value=p1}, preset=1, bank=0, presetBagIndex=2, library=2, genre=2, morphology=2}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pbag][0] NTParsedPresetZone{generatorIndex=0, modulatorIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pbag][1] NTParsedPresetZone{generatorIndex=0, modulatorIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pbag][2] NTParsedPresetZone{generatorIndex=2, modulatorIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pbag][3] NTParsedPresetZone{generatorIndex=2, modulatorIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pgen][0] NTParsedPresetZoneGenerator{generatorOperator=43, amount=NTGenericAmount{value=32512}}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pgen][1] NTParsedPresetZoneGenerator{generatorOperator=41, amount=NTGenericAmount{value=2}}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pgen][2] NTParsedPresetZoneGenerator{generatorOperator=43, amount=NTGenericAmount{value=32512}}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pgen][3] NTParsedPresetZoneGenerator{generatorOperator=41, amount=NTGenericAmount{value=1}}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [inst][0] NTParsedInstrument{name=NTInstrumentName{value=i0}, instrumentIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [inst][1] NTParsedInstrument{name=NTInstrumentName{value=i1}, instrumentIndex=3}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [inst][2] NTParsedInstrument{name=NTInstrumentName{value=i2}, instrumentIndex=6}

Note that file contains two presets, p0 and p1, and those presets link
to the i0 and i2 instruments via the pgen[1] and pgen[3] entries.
However, according to the spec, I have to read pbag entries starting at
the presetBagIndex for each preset. So, when I read pbag[0] and pbag[1]
as part of interpreting p0, I encounter:

TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pbag][0] NTParsedPresetZone{generatorIndex=0, modulatorIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pbag][1] NTParsedPresetZone{generatorIndex=0, modulatorIndex=0}

Duplicate entries that both point to pgen[0]. Not pgen[0] and pgen[1]
as I would expect.

I've inspected the data in a hex editor and those duplicate entries are
definitely present. Multiple SF2 implementations (including Fluidsynth)
parse and play them correctly.

How do I interpret those presets such that I can correctly link them to
the instrument definitions?

--
Mark Raynsford | http://www.io7m.com


_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev

attachment0 (235 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Tom M.
> Specifically, I'm seeing pbag entries in valid and playable
> SoundFonts that don't make any sense to me, and I can't seem to find
> an explanation for them in the spec.
> [...]
> Duplicate entries that both point to pgen[0]. Not pgen[0] and pgen[1]
> as I would expect.

IMO it's valid to do this. Section 7.3 of the Spec says:

"Because both the generator and modulator lists are in the same order as the
preset header and zone lists, these indices will be monotonically increasing
with increasing preset zones. [...] If the generator or modulator indices are
non-monotonic [...], the file is structurally defective and should be rejected
at load time."

A function f is monotonically increasing, if for all x and y such that x <= y
one has f(x) <= f(y). So, although pointless, specifying bags with the same
index multiple times does not contradict this monotonic property.

> However, according to the spec, I have to read pbag entries starting at
> the presetBagIndex for each preset. So, when I read pbag[0] and pbag[1]
> as part of interpreting p0, I encounter:
> TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pbag][0]
NTParsedPresetZone{generatorIndex=0, modulatorIndex=0}
> TRACE com.io7m.jnoisetype.vanilla.NTParsers: [pbag][1]
NTParsedPresetZone{generatorIndex=0, modulatorIndex=0}
>
> How do I interpret those presets such that I can correctly link them to
> the instrument definitions?

Section 7.5 says:

"The preset zone’s wGenNdx points to the first generator for that preset
zone."

Unfortunately this section is lacking a sentence like:

"The number of generators present for a preset zone is determined by the
difference between the next higher preset zone’s wGenNdx and the current
preset’s wGenNdx."

However the PMOD and PBAG sections have such statement, so the fact that PGEN
misses this hint is just a minor bug.

In your case this means:

pbag[0] points to genNdx=0, pbag[1] ponits to genNdx=0; 0 - 0 = 0, i.e.
pbag[0] has zero generators assigned to it.

pbag[1] points to genNdx=0, pbag[2] points to genNdx=2; 2 - 0 = 2, i.e.
pbag[1] has two generators assigned to it, the first one is pgen[0]
(keyrange), the second pgen[1] (instrument)

pbag[2] points to genNdx=2, pbag[3] ponits to genNdx=2; 2 - 2 = 0, i.e.
pbag[2] has zero generators assigned to it.

pbag[3] points to genNdx=2, it is also the last zone, so it just takes all the
residual generators, i.e. two: the first one is pgen[2] (keyrange), the second
pgen[3] (instrument)

Hope that helps. Haven't checked whether that corresponds with fluidsynth
implementation though.


Tom




_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Mark Raynsford
On 2019-02-22T22:06:24 +0100
"Tom M." <[hidden email]> wrote:
>
> A function f is monotonically increasing, if for all x and y such that x <= y
> one has f(x) <= f(y). So, although pointless, specifying bags with the same
> index multiple times does not contradict this monotonic property.

Yep, I agree.

> Section 7.5 says:
>
> "The preset zone’s wGenNdx points to the first generator for that preset
> zone."
>
> Unfortunately this section is lacking a sentence like:
>
> "The number of generators present for a preset zone is determined by the
> difference between the next higher preset zone’s wGenNdx and the current
> preset’s wGenNdx."
>
> However the PMOD and PBAG sections have such statement, so the fact that PGEN
> misses this hint is just a minor bug.
Thanks, this helps a lot. I see what it means now... wGenNdx points to
the first index (in the same style as PBAG) and I'm actually supposed
to parse n values starting at that index. The way I was interpreting it
originally was that there would be n PBAG values and there'd be a 1:1
mapping between PBAG and PGEN values for a given zone. In other words,
for each PBAG value, I look up the corresponding PGEN value. With the
arrangement of PBAG values I showed in basic.sf2, that would obviously
mean that I'd miss the all-important Instrument PGEN!

--
Mark Raynsford | http://www.io7m.com


_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev

attachment0 (235 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Mark Raynsford
On 2019-02-22T21:29:05 +0000
Mark Raynsford <[hidden email]> wrote:
>
> Thanks, this helps a lot. I see what it means now... wGenNdx points to
> the first index (in the same style as PBAG) and I'm actually supposed
> to parse n values starting at that index. The way I was interpreting it
> originally was that there would be n PBAG values and there'd be a 1:1
> mapping between PBAG and PGEN values for a given zone. In other words,
> for each PBAG value, I look up the corresponding PGEN value. With the
> arrangement of PBAG values I showed in basic.sf2, that would obviously
> mean that I'd miss the all-important Instrument PGEN!

I take that back, partially. I'm still not sure about this. Here's the
output from a trivial SF2 that just has a single instrument and doesn't
have any presets (http://ataxia.io7m.com/2019/02/23/trivial.sf2):

TRACE com.io7m.jnoisetype.vanilla.NTParsers: [inst][0] NTParsedInstrument{name=NTInstrumentName{value=i0}, instrumentZoneIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [ibag][0] NTParsedInstrumentZone{generatorIndex=0, modulatorIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [ibag][1] NTParsedInstrumentZone{generatorIndex=0, modulatorIndex=0}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [igen][0] NTParsedInstrumentZoneGenerator{generatorOperator=17, amount=NTParsedGenericAmount{value=0}}
TRACE com.io7m.jnoisetype.vanilla.NTParsers: [igen][1] NTParsedInstrumentZoneGenerator{generatorOperator=53, amount=NTParsedGenericAmount{value=0}}

So I start at INST[0]. The spec says:

"The first zone in a given instrument is located at that instrument’s
wInstBagNdx. The number of zones in the instrument is determined by the
difference between the next instrument’s wInstBagNdx and the current
wInstBagNdx."

Well, there isn't a next instrument, so I make the obvious assumption
that all of the zones given in IBAG belong to the one defined
instrument. So, I first inspect IBAG[0] and see that the generator
index field points to IGEN[0]. The spec says, for modulators (it seems
to miss this information in IGEN as it does the corresponding paragraph
in PGEN that you noted was missing):

"The zone’s wInstModNdx points to the first modulator for that zone,
and the number of modulators present for a zone is determined by the
difference between the next higher zone’s wInstModNdx and the current
zone’s wModNdx. A difference of zero indicates there are no modulators
in this zone."

So in other words, in order to know how many elements of IGEN I need to
inspect for the IBAG[0] zone, I need to look in IBAG[1]. Well, the
generator index value in IBAG[1] is 0. So... What does that mean? The
spec says, for modulators, that a difference of zero indicates that
there are no modulators for the zone. Clearly there are generators for
the zone. How do I know how many to consume?

--
Mark Raynsford | http://www.io7m.com


_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev

attachment0 (235 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Tom M.
> So in other words, in order to know how many elements of IGEN I need to
> inspect for the IBAG[0] zone, I need to look in IBAG[1]. Well, the
> generator index value in IBAG[1] is 0. So... What does that mean? The
> spec says, for modulators, that a difference of zero indicates that
> there are no modulators for the zone. Clearly there are generators for
> the zone. How do I know how many to consume?

I think your confusement may arrise from the question whehter ibag[1] is the
terminal zone or not. This is not quite clear to me.

Supposing it is not the terminal, we have the same situation as before:

ibag[0] has zero generators and zero modulators assigned to it.
ibag[1] takes all the residual generators starting at igen[0] and all residual
modulators starting at imod[0] (in case any modulators exist at all).

Supposing ibag[1] is the terminal zone:

ibag[1] shouldn't be accessed, i.e. you should ignore it. ibag[0] will take
all available generators and modulators starting at index 0 respectively.


Tom




_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Mark Raynsford
On 2019-02-24T13:26:01 +0100
"Tom M." <[hidden email]> wrote:
>
> I think your confusement may arrise from the question whehter ibag[1] is the
> terminal zone or not. This is not quite clear to me.

Sorry, I should have made that clear. In this case neither ibag[0] nor
ibag[1] are the terminal zones. The parser I'm writing doesn't expose
the terminal zones (or any of the other various terminal records) to
the application.

> Supposing it is not the terminal, we have the same situation as before:
>
> ibag[0] has zero generators and zero modulators assigned to it.
> ibag[1] takes all the residual generators starting at igen[0] and all residual
> modulators starting at imod[0] (in case any modulators exist at all).

Ah, I see! I see how that could work (specifically, ibag[1] taking
all the remaining data). I can't find anywhere in the spec that makes
it clear that implementations should do this though... What am I
missing?

--
Mark Raynsford | http://www.io7m.com


_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev

attachment0 (235 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Marcus Weseloh
Hi,

Am So., 24. Feb. 2019 um 13:56 Uhr schrieb Mark Raynsford <[hidden email]>:
On 2019-02-24T13:26:01 +0100
"Tom M." <[hidden email]> wrote:
>
> I think your confusement may arrise from the question whehter ibag[1] is the
> terminal zone or not. This is not quite clear to me.

Sorry, I should have made that clear. In this case neither ibag[0] nor
ibag[1] are the terminal zones. The parser I'm writing doesn't expose
the terminal zones (or any of the other various terminal records) to
the application.

And that might actually be your problem. The terminal entries are important to determine the length of the collections. For example, the terminal ibag entry has the wInstGenNdx that you need to determine the number of generators in the previous ibag entry (terminal wInstGenNdx - previous wInstGenNdx). And that method is used in nearly all the collections (modulators, ibags, pbags, ...).

So your parser can't simply ignore the terminal zones.

Cheers,
Marcus

_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Mark Raynsford
On 2019-02-24T14:29:01 +0100
Marcus Weseloh <[hidden email]> wrote:
>
> And that might actually be your problem. The terminal entries are important
> to determine the length of the collections. For example, the terminal ibag
> entry has the wInstGenNdx that you need to determine the number of
> generators in the previous ibag entry (terminal wInstGenNdx - previous
> wInstGenNdx). And that method is used in nearly all the collections
> (modulators, ibags, pbags, ...).
>
> So your parser can't simply ignore the terminal zones.

Ah, to be clear, the parser itself doesn't ignore them. It uses them
internally to determine numbers of records and the like. It's just that
the programmer using the parser doesn't see the terminal record; they
just receive a (possibly empty) list of the available non-terminal
records.

--
Mark Raynsford | http://www.io7m.com


_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev

attachment0 (235 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Marcus Weseloh
(resend to include mailing list)

Hi,

Am So., 24. Feb. 2019 um 14:53 Uhr schrieb Mark Raynsford <[hidden email]>:
On 2019-02-24T14:29:01 +0100
Marcus Weseloh <[hidden email]> wrote:
> So your parser can't simply ignore the terminal zones.

Ah, to be clear, the parser itself doesn't ignore them. It uses them
internally to determine numbers of records and the like. It's just that
the programmer using the parser doesn't see the terminal record; they
just receive a (possibly empty) list of the available non-terminal
records.

I might have misunderstood the logs that you've sent up to now, but it seems to me like your parser only turns the structures in the SF2 file into (lists of) objects and exposes them to the user. The user then needs to follow the pointers to the different structures, for example look up the generators for a given instrument zone. If that is how your parser works and you omit the terminal records from the lists, then you are not giving the user all the information that he needs to make sense of the data. So either the parser itself needs to resolve the links between the data structures before passing the data on to the user, or return the terminal records to the user as well.

Tom suggested that the last instrument should simply take all residual generators from the specified index onward. I think that is actually not according to spec and is also not the way Fluidsynth does it. Fluidsynth strictly checks the terminal ibag for the terminal igen index. There are a few workarounds in Fluidsynth where we ignore missing terminal records. Those are needed because there are some broken Soundfont editors out there. But we do that only where the terminal records isn't actually needed, in other words only in leafs in the Soundfont tree, i.e. preset modulators or generators.

Cheers,
Marcus

Am So., 24. Feb. 2019 um 14:53 Uhr schrieb Mark Raynsford <[hidden email]>:
On 2019-02-24T14:29:01 +0100
Marcus Weseloh <[hidden email]> wrote:
>
> And that might actually be your problem. The terminal entries are important
> to determine the length of the collections. For example, the terminal ibag
> entry has the wInstGenNdx that you need to determine the number of
> generators in the previous ibag entry (terminal wInstGenNdx - previous
> wInstGenNdx). And that method is used in nearly all the collections
> (modulators, ibags, pbags, ...).
>
> So your parser can't simply ignore the terminal zones.

Ah, to be clear, the parser itself doesn't ignore them. It uses them
internally to determine numbers of records and the like. It's just that
the programmer using the parser doesn't see the terminal record; they
just receive a (possibly empty) list of the available non-terminal
records.

--
Mark Raynsford | http://www.io7m.com


_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev
Reply | Threaded
Open this post in threaded view
|

Re: Slightly OT: Parsing SF2; duplicate pbag entries?

Tom M.
In reply to this post by Mark Raynsford
> I see how that could work (specifically, ibag[1] taking
> all the remaining data). I can't find anywhere in the spec that makes
> it clear that implementations should do this though... What am I
> missing?

The wording "residual generators/modulators" I used before is not quite
correct. As already ponited out by Marcus, the terminal ibag tells you how
many generators and modulators to use. Section 7.7:

"The size of the IMOD sub-chunk in bytes will be equal to ten times the
terminal instrument’s wModNdx plus ten and the size of the IGEN sub-chunk in
bytes will be equal to four times the terminal instrument’s wGenNdx plus four.
If the generator or modulator indices are non-monotonic or do not match the
size of the respective IGEN or IMOD sub-chunks, the file is structurally
defective and should be rejected at load time. "

Which in turn means that if (hypothetically) ibag[1] were the terminal zone,
the file would be rejected because the size of the igen subchunk wouldn't
match that requirement (contrary to what I said before, sry).


Tom



_______________________________________________
fluid-dev mailing list
[hidden email]
https://lists.nongnu.org/mailman/listinfo/fluid-dev