Login / Register  0 items | $0.00 New#KVRDeals
xhy3
KVRist
 
33 posts since 19 Jan, 2014

Postby xhy3; Fri Oct 13, 2017 3:38 am Re: Triangle hard sync with BLEP's opinions

aciddose wrote:I use distinct buffers for different orders because I'm using minimum phase. This means each order has a unique delay.

(Removed technical rant about why not.)

So I can't tell you that it does work, but I can say that I have absolutely no reason to believe there is any technical reason that it wouldn't work. It works perfectly now, so simply rearranging the delay and mixing into a single buffer wouldn't change anything of any significance. There is just no benefit to doing so.

Or did you mean that not literally? Yes I use both types (and more, I've experimented with up to 4th order) in the waveforms I posted the graphs for. For example both 1st and 2nd order are used in the 2x pulse * ramp waveform because the delta switches between 0 and 1. Sync works perfectly in all these waveforms and is used when switching waveform. This is why you can see the initial 1st and 2nd order kernels at the beginning of some of the waves.

https://soundcloud.com/xhip/osc-b-demo

This is a clip of the oscillator including those 2nd order waveforms running with sync and PWM in a stereo unison. Several waveforms are demonstrated including pulse, ramp, triangle, 4pt cos, 8pt cos, z ramp, dual ramp, pulse2x chopped ramp and variable width ramp (adjustable slope ramp/triangle).


Thanks for pointing that out and the audio demo. I come to the conclusion that my BLEP's and BLAMP's maybe don't have the same delay (maybe an error in the calculation) or maybe the BLAMP isn't accurate enough.
It looks like while the BLAMP does a perfect job with 32 times oversampling but the quality gets massive low with x128 oversampling. How did you generate the higher order bleps?
mystran
KVRAF
 
4611 posts since 11 Feb, 2006, from Helsinki, Finland

Postby mystran; Fri Oct 13, 2017 3:40 am Re: Triangle hard sync with BLEP's opinions

I would personally keep the current segment of any piece-wise waveforms (eg. pulse, triangle..) in an explicit integer variable. This way all you need to check is whether the phase (at the end of the sample) is larger than the end-point of the current segment. If that's the case, you can solve the exact time backwards using the current frequency. If you're advancing the slave in preparation for a master reset, you can then check if that time is past the scheduled master reset time and break out of the (transition handling) loop if that's the case. This way it's also totally obvious if you need a slope corrector for triangle or not: just look at the segment-variable to know if we're in the first or the second segment.
Image <- plugins | forum
xhy3
KVRist
 
33 posts since 19 Jan, 2014

Postby xhy3; Fri Oct 13, 2017 4:42 am Re: Triangle hard sync with BLEP's opinions

mystran wrote:...you can solve the exact time backwards using the current frequency.


Maybe i missed that. How would you do that. I use following:

Code: Select all
if (segment == 2) newPhase = 0.5f - (phase - 0.5f);


How can i bring in the frequency to get the right result?
mystran
KVRAF
 
4611 posts since 11 Feb, 2006, from Helsinki, Finland

Postby mystran; Fri Oct 13, 2017 12:35 pm Re: Triangle hard sync with BLEP's opinions

xhy3 wrote:
mystran wrote:...you can solve the exact time backwards using the current frequency.


Maybe i missed that. How would you do that. I use following:

Code: Select all
if (segment == 2) newPhase = 0.5f - (phase - 0.5f);


How can i bring in the frequency to get the right result?


If you have constant frequency over the sample (let's call it "deltaPhase") then we have:

newPhase = phase + deltaPhase

we can solve this back the other way too:

phase = newPhase - deltaPhase

Now if we wanted to solve for the fractional sample time t where phase = .5, we'd have:

phase + t*deltaPhase = .5

now substitute from above, and solve for t:

(newPhase - deltaPhase) + t*deltaPhase = .5
newPhase + (t-1)*deltaPhase = .5
(t-1)*deltaPhase = .5 - newPhase
(t-1) = (.5 - newPhase) / deltaPhase
t = 1 + (.5 - newPhase) / deltaPhase.

Once you know the exact time-point where the transition occurs, rest of it shouldn't be very complicated. :)
Image <- plugins | forum
User avatar
aciddose
KVRAF
 
11568 posts since 7 Dec, 2004

Postby aciddose; Fri Oct 13, 2017 4:37 pm Re: Triangle hard sync with BLEP's opinions

mystran wrote:I would personally keep the current segment of any piece-wise waveforms (eg. pulse, triangle..) in an explicit integer variable. This way all you need to check is whether the phase (at the end of the sample) is larger than the end-point of the current segment.


That obviously doesn't work if you're using negative frequencies. Either you need to generate two tables, one forward and one reverse, or each step in the table needs both start and end values.

Ultimately no matter what you do the result contains redundant information (bug prone) and the computation is less efficient for basic 1-step or 2-step waveforms like ramp, pulse and triangle.

That's why I recommended that anyone should start by handling all these computations without additional premature optimizations like storing the last known step, waveform or other variables.

It's certainly a possible optimization in the case that it is only applied to waveforms with >2 steps and where negative frequencies aren't allowed. In other cases however there are more optimal solutions for those specific cases.
xhy3
KVRist
 
33 posts since 19 Jan, 2014

Postby xhy3; Sat Oct 14, 2017 8:06 am Re: Triangle hard sync with BLEP's opinions

mystran wrote:
xhy3 wrote:
mystran wrote:...you can solve the exact time backwards using the current frequency.


Maybe i missed that. How would you do that. I use following:

Code: Select all
if (segment == 2) newPhase = 0.5f - (phase - 0.5f);


How can i bring in the frequency to get the right result?


If you have constant frequency over the sample (let's call it "deltaPhase") then we have:

newPhase = phase + deltaPhase

we can solve this back the other way too:

phase = newPhase - deltaPhase

Now if we wanted to solve for the fractional sample time t where phase = .5, we'd have:

phase + t*deltaPhase = .5

now substitute from above, and solve for t:

(newPhase - deltaPhase) + t*deltaPhase = .5
newPhase + (t-1)*deltaPhase = .5
(t-1)*deltaPhase = .5 - newPhase
(t-1) = (.5 - newPhase) / deltaPhase
t = 1 + (.5 - newPhase) / deltaPhase.

Once you know the exact time-point where the transition occurs, rest of it shouldn't be very complicated. :)


I'm getting huge values when i use that formula. Do i use it when i insert the 0.5 blamp and remeber it for the hard sync?
User avatar
aciddose
KVRAF
 
11568 posts since 7 Dec, 2004

Postby aciddose; Sat Oct 14, 2017 3:13 pm Re: Triangle hard sync with BLEP's opinions

Why don't you just use the set_phase() implementation I gave you? It works correctly.

There is absolutely no reason not to use an inline version of set_phase() in your main loop. The compiler can optimize it as needed, especially if you use templates: template <typename wave> set_phase(...) { wave wf; wf.get_level_at(phase); ... }

The compiler will produce better optimized code than you ever could by hand and you'll be able to use the same implementation for any waveform you want.

That's assuming of course that you don't want to be able to dynamically modify the waveform and have any changes correctly anti-aliased as well. In which case the very minor overhead of a function call is well worth it when it is 1/00th as expensive as inserting a single FIR kernel.

Premature optimization is the root of all evil. You might think you know better "I don't need X or Y", but without having a full implementation how can you possibly know all the possible optimizations in order to choose the best available optimization? You can't.

The first step is to write working code.

The next step is to optimize it. That includes profiling and bench-marking to ensure your "optimization" is actually any advantage at all against the reference implementation.

How can you perform profiling and bench-marking without a reference implementation? How can you write unit tests without a reference implementation? You can't.

Code: Select all
float ramp_to_triangle(const float x)
{
   return std::abs(x * 2.0f - 1.0f) * 2.0f - 1.0f;
}

void blepgenerator::tests::triangle()
{
   const blepdata::impulse_t &impulse = blepdata::get_minblamp_impulse();
   ad::dsp::blep_generator<> b;
   wave_t wave(1, 32, rate, length, format_t::ieee_float);
   float *p = (float *)&wave.data[0];

   const float gain = std::sqrt(1.0f / 2.0f);
   const float delta = hz / float(rate);
   float phase = 0.49f;
   float next = 0.5f;

   for (unsigned int i = 0; i < wave.samples; i++) {
      phase += delta;

      if (phase >= next) {
         if (next >= 1.0f) {
            phase -= 1.0f;
            b.delta.add(phase / delta, -2.0f * delta, impulse);
            next = 0.5f;
         } else {
            b.delta.add((phase - next) / delta, 2.0f * delta, impulse);
            next = 1.0f;
         }
      }

      p[i] = gain * b(ramp_to_triangle(phase));
   }

   wave.store("./triangle.wav");
}


Simple tests like this are essential. You should also write unit tests for every component part.

16 samples per 8 zero crossings, minimum phase 2nd order:
http://xhip.net/temp/triangle_16bit.wav
Apparently most browsers won't play floating point RIFF wave format http://xhip.net/temp/triangle.wav
xhy3
KVRist
 
33 posts since 19 Jan, 2014

Postby xhy3; Wed Oct 18, 2017 12:26 am Re: Triangle hard sync with BLEP's opinions

aciddose wrote:...
The compiler will produce better optimized code than you ever could by hand and you'll be able to use the same implementation for any waveform you want.

That's assuming of course that you don't want to be able to dynamically modify the waveform and have any changes correctly anti-aliased as well. In which case the very minor overhead of a function call is well worth it when it is 1/00th as expensive as inserting a single FIR kernel.

Premature optimization is the root of all evil. You might think you know better "I don't need X or Y", but without having a full implementation how can you possibly know all the possible optimizations in order to choose the best available optimization? You can't.

The first step is to write working code.

The next step is to optimize it. That includes profiling and bench-marking to ensure your "optimization" is actually any advantage at all against the reference implementation.

How can you perform profiling and bench-marking without a reference implementation? How can you write unit tests without a reference implementation? You can't.

...

Simple tests like this are essential. You should also write unit tests for every component part.

16 samples per 8 zero crossings, minimum phase 2nd order:
http://xhip.net/temp/triangle_16bit.wav
Apparently most browsers won't play floating point RIFF wave format http://xhip.net/temp/triangle.wav


I totally agree with you. My code is not optimized and i don't fear any methods. It's just pragmatic... i wanted a pulse, saw or triangle with hard sync and programmed it only for this cases. For me, the second step is a general solution for all cases.
I like the idea of unit tests for the oscillators. Maybe even compare the file to a reference file to be sure that it still works the way it should. Not sure about float results, but it should work for 16 bit output.
User avatar
aciddose
KVRAF
 
11568 posts since 7 Dec, 2004

Postby aciddose; Wed Oct 18, 2017 2:20 am Re: Triangle hard sync with BLEP's opinions

Float isn't a problem, just use abs(x - y) < threshold.

Some very useful tests actually use some measurement of the spectrum compared against as well as with the expected harmonics ignored. You can then measure pass-band effects from the filter, aliasing amplitude, slope and other very useful properties.

If you get your oscillator working and can accurately measure these properties you can then compare those test results rather than comparing one signal directly against another.

Possibly with:
assert(stop_band <= expected_stop_band);
assert(pass_band_error <= expected_pass_band_error);

And so on.
User avatar
aciddose
KVRAF
 
11568 posts since 7 Dec, 2004

Postby aciddose; Wed Oct 18, 2017 8:06 pm Re: Triangle hard sync with BLEP's opinions

Since for example we're entirely capable of generating perfect repeating waveforms at exactly the window width (width/n) because the FIR kernel has a finite length. It means we don't even need to window the signal in order to generate a fourier bode plot (spectrum + phase).

xcope0.png


This means there is no need to worry about side-bands or resolving capability, you essentially end up with exact amplitudes for each individual harmonic.

The stop-band level in this example is about -80 to -110 dB (depending on peak vs. average) and the pass-band error is significant at over 20 dB (peak is likely not the best measurement in general, but it works.)
You do not have the required permissions to view the files attached to this post.
Previous

Moderator: Moderators (Main)

Return to DSP and Plug-in Development