Wednesday, August 24, 2005

clumping

The clumping problem goes all the way back to the beginning. It occurs when master speed and/or ring growth) are big enough to cause multiple rings per timer tick. When the view's timer hook adds multiple rings, the rings all share the same properties, which causes those rings to form an easily distinguishable group or "clump".

The worst offenders are rotation and skew (AKA shift). Both of these are zeroed at birth, and then incremented by the view's draw function. A major reduction of clumping can be obtained simply by offsetting each ring's initial rotation and skew. The offset is a delta multiplied by a fraction. The fraction is calculated by dividing the ring's index by the total number of rings that will be born for this timer tick. If four rings will be born, the fractions for the rings will be 0, .25, .5, and .75 respectively, and if the current rotation delta is .2, the initial rotations for the rings will be 0 * .2, .25 * .2, .5 * .2, and .75 * .2.

The only complication is that the rings are generated in reverse order. This means the indices have to be inverted, e.g. by using (count - 1 - i) instead of i, which means the count must be computed before the ring-adding loop begins. The count can be computed as:

int(m_st.RingOffset / m_Parms.RingSpacing) + 1;

Also note that the resulting offsets must be multiplied by RingGrowth to be effective.

The next-worst offenders are the LFOs. The LFOs are outside the view, but they only modify the view parameters on timer tick boundaries. This means that any LFO has the potential to cause clumping. The high road would move the oscillators into the view but this is a drastic change and has many side effects. The low road is to interpolate between the current and previous parameter values. In practice interpolation seems to work very well for all parameters except ring growth and ring sides. The view timer hook must calculate a delta between each parameter's current and previous values, like so:

for (int j = 0; j < 16; j++)
delta[j] = (((double *)&prevparm)[j] - ((double *)&m_Parms)[j]) / count;

And then within the ring-adding loop, overwrite m_Parms with interpolated values:

for (j = 0; j < 16; j++) // NOTE: don't do this to ring growth or # sides
((double *)&m_Parms)[j] = ((double *)&prevparm)[j] + delta[j] * (count - 1 - i);

And finally, restore m_Parms.

Interestingly, this seems to work perfectly with sine LFOs, but exhibits a noticeable periodic discontinuity with triangle LFOs. This seems to indicate an undiscovered problem with the above method, or possibly a bug in the oscillator object.

The final offender is the ring color. This is calculated in the view's timer hook, which means all the rings in a group will have the exact same color. This can probably be solved by interpolation as well.

No comments: