Hi Everyone,
Here's the document I promised a while back, basically
detailing how sfront issues that may be the suject of future
corrigendums, along with a few suggestions for corrigendum that
aren't presently implemented in sfront.
The document comes in two parts
[1] Part 1 is in an "Sfront Implementation Issues FAQ" format. For
most of these, there was email between Eric and I about the
point. Sometimes, I implemented sfront based on my understanding of
the exchange; other times, the exchange ended with a problem
identification but not a solution, and the sfront implementation was a
solution I came up with on my own, to get a shipping decoder out the
door.
[2] Part 2 is a list of newer potential corrigendum. Some of these
are implemented today in sfront, some of these represent requests from
the community I've received.
--jl
-------------------------Part 1--------------------------------
Sfront Implementation Issues Frequently Asked Questions List
Version 0.1, May 10 2000
John Lazzaro
University of California at Berkeley
CS Division
Some sfront behaviors don't match the ISO/IEC 144996-3:1999(E)
standards document, or don't match saolc behaviors. Many of these
differences are do to anticipated changes in the spec in upcoming
corrigendum. This document describes these changes in detail.
Q1. Sfront's operator precedence doesn't seem to match saolc's or
the specification. Why?
A1. Sfront's operator precedence matches C, which I have proposed to
Eric as a corrigendum. Here's sfront's operator precedence in human
readable form:
Ops Associativity Order
?: right to left lowest precedence
|| left to right
&& left to right
== != left to right
< <= > >= left to right
+ - left to right
* / left to right
- ! right to left highest precedence
and in yacc/bison form:
%left Q
%left OR
%left AND
%left EQEQ NEQ
%left LT GT LEQ GEQ
%left PLUS MINUS
%left STAR SLASH
%right UNOT UMINUS
%token HIGHEST
Q2. How does sfront's MP4 encoder handle the concat waveform generator
when used in SASL table commands? The bitstream doesn't seem to be right
here.
A2. Indeed, concat generator has tables as arguments, but the bitstream
doesn't have a way to specify symbols. Eric suggested this solution,
which sfront implements.
In the definition of "class table_event" in the bitstream format, replace
unsigned float(32) pf[num_pf];
with
if (tgen==0x7D) /* concat */
unsigned float(32) size;
symbol ft[num_pf-1];
} else {
unsigned float(32) pf[num_pf];
}
Q3: Sfront doesn't like one of Eric's saolc ASCII examples, that uses
min and max as variable names? Who's right here?
A3: Part 5.8.2.2 of the spec says that an identifier can't be a
reserved word, standard name, core opcode, or core wavetable. Sfront
implements this restriction, whereas saolc does not.
Q4: Sfront's buzz operator's C code doesn't seem to match the spec.
What's up?
A4: Several things:
[1] The cos(f*()) in the spec needs to be cos((f+1)*...), lest the
lowharm=0 results in a DC component being produced (since
cos(0*()) = 1).
[2] If rolloff == 1, scale should == 1, as Eric describes in documentation
that comes with saolc.
[3] For the math to work over a wide range of rolloff's in floating
point, the definition of "scale" wants to be pushed into the summation,
so that the floating-point numbers stay within range.
[4] Sfront implements a closed-form solution to the buzz opcode math,
which makes its computation-time constant with respect to number of
harmonics. See saol-devs archives, or sfront's C code, for details on
this solution.
Q5: Sfront handles template structures with expressions in the
preset and maplists, and saolc doesn't, what's up?
A5: As specified in the standard, templates are not a context free
grammer, and so yacc can't handle it. Sfront has a lexical analyzer
that does multi-token lookahead, and so correctly handles all template
syntax shown in the spec. Thanks to Ross Bencina.
Q6: Sfront's expseg's wavetable generator math doesn't seem to match
the spec, what's up?
A6: This part of the spec:
"x in the range x2 through x3 shall be set to y2*(y3/y2)^(x-x1)/(x2-x1)"
is incorrect: in the exponent, x2 should be x3, x1 should be x2.
Q7: Sfront's gaussian window math in the window wavetable generator
doesn't match the spec, what's up?
A7: The window math in the spec produces a window so narrow as to be
useless (at least, it seemed this way to me, not impossible the spec
is right and I'm wrong, but I checked several times). I went to the
library and looked up math for "Gaussian Windows" in a IEEE article
tutorial on windowing, and found this math, which sfront implements,
for the Gaussian Window:
c1 = 18/(size*size)
c2 = size/2
x = 0 to size-1
win[x] = exp(-c1*(c2-x)*(c2-x))
Q8: The tableread and tablewrite spec says that its legal to use an
index value of the table size, but sfront complains. Why?
A8: I wasn't really sure what to do here -- these operators don't
define the index values to be modulo the table size. Given this,
since tables have indexes between 0 and size-1, an integral table
index of size must be illegal. For tableread, any value up to size is
OK, because the opcode interpolates fractional indices. For tablewrite,
any value great or equal to ((size-1)+0.5) will be in trouble, because
tablewrite rounds to the nearest integral value. So this is what sfront
implements today. However, it may be more SAOL-programmer friendly to
implement something else -- either an exception allowing indexes of
value size that wrap around to zero, or full blown modulo indices.
Q9: What does the grain operator do with the ampl parameter? The
spec doesn't say?
A9: Sfront uses it to scale the value of the grain, under the
assumption that this is a bug in the spec.
Q10: The math in the biquad core opcode is wrong! What does sfront
really implement?
Here's the correct version:
to = w2 + b0*ti
w2 = w1 - a1*to + b1*ti
w1 = - a2*to + b2*ti
Q11: There seems to be some discrepecencies between the FFT/IFFT
spec and sfront's implementation. What's up?
A11: Several things. First of all, in the IFFT spec:
(out[i] += seq[i]*win[i] for 0 < i < len)
should be:
out[i] += seq[i] * win[i] * shift/len
In addition, sfront currently assumes that the len's in
the FFT and IFFT equations should be size's.
Q12: The compressor opcode definition is wrong! Does sfront
implement it as is?
A12: No, I guessed at what the definition was trying to
do, and implemented that instead. Here are excerpts from
the spec and how I interpeted this. Since I wrote this I may
have fixed a few sfront bugs involving compressor, though, so
a detail or two may be off -- check the source code.
**********quoted material*******
3. The amplitude multiplier gain is
calculated as follows:
[...]
else if (env == hiknee)
gain = 10^ ...
********************************
Comment: It looks like this
should be
else if (env >= hiknee)
gain = ratio;
The original equation seems based in
a mixup between the value of the
gain (which is the slope of the graph
on p 120) and the actual value of the
graph on page 120. In a similar spirit:
**********quoted material*******
Gain should be interpolated smoothly
between ...
********************************
Comment:
Details of interpolation in this paragraph
also seems to suffer from this problem.
Since ratio may be less than 1, a monotonically
increasing interpolation may be impossible,
in addition, the endpoint slopes of the gain
function are both zero (constant gain of 1 and
ratio). It looks like a linearly interpolated
gain results in the graph on page 120 having
the monotonically increasing, continuous derivative
properties you're looking for.
**********quoted material*******
At each rate call to compressor(), the
following happens:
[...]
2. The next envelope is calculated as follows:
[..]
********************************
Comment: Pseudocode needs a "change = 0" at
top, as well as some "{" "}" to make the
grouping clear.
**********quoted material*******
ratio is the ratio of compression above the
knee [...] Negative numbers perform an
inversion in addition to compression and
expansion.
********************************
Comment: Given the interpolation needed, the
way to compute this correctly involves taking
the absolute value of ratio and using it for
all computation, then multiplying the inversion
into the final answer. It would also be good
to clarify if this inversion is only supposed
to happen for all signal intensity, or only above
hiknee -- i.e., is an instantaneous 180 degree
inversion supposed to happen as env passes
though hiknee? I assumed the former, and scaled
the output by the sign of ratio throughout the
range of env.
Q13: Shouldn't All Sound Off have the same MIDI behaviors as
All Notes Off?
A13: Yes, sfront handles this correctly, but the spec doesn't
mention All Notes Off yet.
Q14: A careful reading of the spec shows that:
aopcode foo(asig a[outchannels]) {
asig b[outchannels];
}
Results in different meanings for the outchannels in the
opcode declaration (the number of output channels for the
entire orchestra) and the variable declaration (number of
output channels for the instrument using the opcode) Can
this be true?
A14: Yes, sfront implements this behavior currently
as specified by the spec.
Q15: There seems to be some missing and/or irrelevent tokens
in Annex 5.A. What's up?
A15: Depending on what version of the spec you have, these
token issues are outstanding:
[1] Startup token missing -- will be 0x4B (also, its startup, not
start-up).
[2] Token table has a destroy token, but its seemingly not needed,
since the SASL table line's bitstream class has a dedicated destroy
bit.
[3] Use 0x49 and 0x4A token numbers for input_bus and output_bus
[4] The EOO token number is correct, although your version of saolc
may not use/respect it.
Q16: How should the sample wavetable generator be encoded in class
table_event?
A16: The actual sample is coded by table_sym. However, you should also
leave an empty position in pf[] for the "which" entry, rather than
decrease the number of pfields by one.
Q17: Why does saolc and sfront dislike using non-xsig parameters in
the parameter and variable declarations of poly-ops?
A17: According to Eric, the spec's intention is that a polyopcode
produce legal parameter and variable declarations no matter what
rate it resolves to. Since this wouldn't be possible if ksig or
asig parameters and variables are declared, saolc makes these illegal
(and ivar's too to be consistent) and sfront follows that lead.
Q18: I'm having trouble understanding how to implement both user-defined
and core-opcodes, with respect to rate-semantics. Any tips?
A18: Eric and I have exchanged many emails over this, here are a
set of axioms I've implemented in sfront regarding these issues:
[1] A core kopcode or iopcode called at a-rate, or an iopcode called
at krate, implements its semantics as specified in the standard
correctly. In other words:
ksig k;
asig a;
k = kphasor(1);
a = k*aphasor(1);
computes the same value of "a" as:
asig a;
a = kphasor(1)*aphasor(1);
[2] User-defined opcodes follow the "UDO-rule":
"If a user-defined opcode is called at a certain rate, only
statements in the opcode body of that rate execute."
This has several implications:
[a] User-defined opcodes should have all signal variable declarations,
and all statements, be at the rate of the user-defined opcode (i.e.
(kopcodes should have ksig declarations and all lines should be
k-rate lines). This is needed because (in the case of the kopcode
example) any i-rate statements aren't going run when the opcode
is called at k-rate. And given this fact, there's no way for any
ivars to get assigned values.
[b] The sentiments in [1] hold if the user-defined opcode is a poly-op,
see Q17 for details.
[c] Unlike a core opcode, calls to a (for example) kopcode should
always be like this:
ksig k;
asig a;
k = userkopcode(1);
a = k*userkopcode(1);
to work, because the UDO-rule says this won't work:
asig a;
a = userkopcode(1)*aphasor(1);
Sfront gives a warning error with the above construct, and sets
a to the value 0.
[d] User-defined opcodes can have parameters slower than the opcode
rate -- this is OK because these parameters can be used in the
right-hand side of expressions.
[e] One could imagine trying trickery involving poly-opcodes and
oparrays, to create opcodes that have statements at multiple
rates and that would seem to run correctly given the spec. Don't
try to implement this in a decoder, its not intended to be
a valid way to use polyops.
-------------------------Part 2--------------------------------
These are potential corrigendum issues, listed by their specification
section number.
5.5.2
Summary: Clarifying midi_event class
Midi_event is defined as holding a single
MIDI command, such as a NoteOn, but yet
has 24 bit length. This seems like a big
waste of bits -- can a Midi-event class in
fact hold many MIDI commands destined for
the same SA_access_unit? On a related
note, is running status legal in a midi_event
(presumably using the command byte of the
previous midi_event)?
5.8.5.5
Summary: Can't end SAOL program execution from SAOL, only SASL
Algorithmic music people have been complaining to me that
there's no algorithmic way to end a SAOL program from
SAOL itself -- i.e. the equivalent of the SASL end statement
semantics, but from SAOL. I include it as a corrigendum
to 5.8.5.5 because of my suggested fix (not elegant, but
requires the smallest change to the standard) -- make it
legal to execute the turnoff statement in an instance of
an instrument created by the send() statement and sent
the special output_bus, and have the semantics of this
turnoff be the end of the orchestra. Or perhaps a simple
core opcode call to do this would be better.
5.8.6.6.2
Summary: params[] not listed as an lvalue
If I read the MPEG 4 Systems document correctly, params[]
is meant to be for bi-directionaly communication between
the SAOL program and the decoder itself. But this section
on the assignment statement doesn't indicate that it can
be an lvalue, although it does make that exception for
MIDIctrl[].
5.9.6.12-15
Summary: Negative Frequency Clarification
The description of these table opcodes should be explicit
about the behavior when zero or negative frequencies are
specified. This is most needed in loscil(), where its
ambigious what to do during different portions of the
loscil state machine (i.e. if you pass the loop start
but not the loop end going forward, then go backward,
to do proceed to the start of the sample or do you
take the loop backward. Ect). Might be good to check
the signal generators on this regard too.
5.9.7.1
Summary: Envelope Generator Behavior At End of Envelope
{k,a}{line,expo} are defined as returning zero after the
envelope is complete. For envelopes with a non-zero
xn, this causes a click, and "programming around this",
while technically simple, is hard to explain to nascent
music programmers. In addition, I've heard (but not
verified) that saolc in fact returns xn after the
envelope is complete. Should this be the standard way
to do it instead? If so, are there other a-rate opcodes
that return 0 after completion (like table ops) that
should be reworked in this way also (probably not ...)?
5.9.7.8
Summary: Buzz definition at high rolloff^nharm values.
If the buzz definition is implemented literally, and
if users use the "nharm = -1" option in conjunction
with lower freq values (inducing high nharm), the
value of scale will calculate out to zero if computed
directly using floats. The "right" behavior here is
to push scale into each rolloff term, so that the
large numerator and large denominator of the constant
in front of the highest-frequency sine waves cancel,
yielding a tone dominated by high frequences. The
"naive" computing approach yields a zero value waveform.
The text of the buzz definition should warn of this.
It perhaps should also point people to the low-complexity
series approximations for this formula also.
5.9.7.9
Summary: Looped waveforms for grain lose attack.
The definition of the "loscil-like" grain mode just
plays the loop itself, not the attack portion. This
change (I believe) happened relatively recently. Is
this on purpose, or an editing error?
5.10.9
Summary: Spline run-time error spec incorrect
The equation for the number of valid needed
parameters (not including size) for spline
is 2 + N*3 -- this can be odd as well as
even, and so "run-time error if there are
an odd number of parameters" is incorrect.
5.11
Summary: SASL ASCII files and newlines
Saolc behaves badly on SASL files with
blank lines, so I assume that an ASCII
SASL file with blank lines is not valid.
Perhaps this should be said explicitly.
Alternatively, if SASL files become
looser in this regard, I've gotten
several requests for allowing comments
in SASL files -- in fact, sfront supports
// comments in SASL files today, although
its not documented.
5.11 and 5.7.3.3.5.4
Summary: First Pass of Effects Instr and SASL Control Commands
The decoder execution ordering always uses "dispatch time
earlier than current time" to describe the dispatch process.
If only positive dispatch times are allowed in SASL files,
an issue arises at the start of an orchestra. The effects
instruments exist during the first execution cycle (zero
time), but if these effects instruments are being initialized
by importing global variables that are being set by SASL
control commands, those SASL control commands (even if given
timestamp 0.0) won't execute until the second cycle, because
"0 is not earlier than 0". Unless, of course, there's roundoff.
The mixer.saol example on the SAOG website from Studer uses
this coding technique, and the initialization words under
saolc because of roundoff, but not under sfront. The easy
solution is to specifically allow negative score times for
SASL lines, which a cursory reading seems to neither allow
or disallow. Without this, effects instrs cannot be reliably
initialized from SASL.
5.14.3.2.13
Summary: tempochange, MIDI, and MIDIfiles
Its probably good to note in this section
that the tempochange you're describing here
is a meta-event from the MIDI File Standard
(which piggybacks on the unneeded FF system
reset command byte), and not part of the
mechanism for tempo on actual MIDI-on-a-wire
(sending clocks, ect).
5.14.3.3.2
Summary: midi_events and extended channels
Neither of the pointers (5.14.3.3.4 or 5.F.2)
explains how to code extended channels for
midi_events. It would be good to either say
[1] only 16 channels for midi_events or
[2] point to a third place that describes
how. The MIDI Port meta-event in the MIDI
File Format standard woule be one way to
support extended channels in midi_events,
see http://www.borg.com/~jglatt/tech/midifile.htm
search for MIDI Port (its command FF 21 01 pp).
5.14.3.2
Summary: Midictrl[] in effects instr
SAOL programs that use MIDI as well as SASL, effects,
and dynamic instruments sometimes need to see
the MIDIctrl[], MIDIbend, and MIDItouch values.
As suggested by Robin Davies, sfront currently
implements the concept of a "MIDI Master Channel",
whose values are those seen by SASL and effects
intruments reading MIDIctrl[], MIDIbend, and
MIDItouch values. Dynamic instruments inherit the
source of their parents for these standard names.
The "MIDI Master Channel" was defined as:
-- if a real-time control source present, by
default extended MIDI channel 0 of this source
-- if not, and if an SA_access_unit MIDI source
is present, the extended channel number that maps
to channel 0 of the MIDI source.
-- if no real-time or SA_access_unit MIDI source
available, the extended channel number of channel
0 of the first MIDI file track that contains MIDI
data on it (i.e. NoteOn, NoteOff, CChange, ect).
This scheme may not be appropriate as is for
standardization, but there is a clear need for
getting MIDIctrl[] info to effects instruments, so
that sliders can be linked to effects.
This archive was generated by hypermail 2b29 : Mon Jan 28 2002 - 12:03:54 EST