can you test scrubs for me? Both munger~ and scrubs are from percolate and i have been going through it and fixing makefiles so it compiles on raspi so before i make menus etc… i wanted to test them outside my echo chamber

attach some live audio input scrubs sounds very interesting with drums but really anything you like
the aux button turns on/off the scrubber [fingers crossed]
scrubs.zip (335.0 KB)

1 Like

I’m not sure what kind of feedback you’re looking for.
It loads, and I assume it works.
A few notes :

  • a dry/wet would be essential. When it’s off (pressing aux, led off), there’s no sound.
  • it loads with the process enabled, but no led lit.
  • when you hit first key, the message “zeroing delay lines” should disappear after one second or so.

It makes nice glitchy sounds, but I don’t really get what I’m doing to be honest.

After checking your patch.
-There is a missing patchchord before the delay that goes to the “done” message to replace “zeroing delay lines”.

  • indeed no dry/wet handling and no init for the led state.

the feedback i was looking for was/is much simpler–
before progressing was if the externals load without needing to install a shared object (linux file *.so)

lights and menu stuff is usually last and i agree with those of course but thanks for trying it!

1 Like

Hello all,

A friend mentioned you might find this interesting and possibly have some insight as the causes for what I’m hearing:

I’ve found that by running very low frequencies (0.005 - 20 Hz) through the stock high-pass filter in pure data ([hip~]) three types of artifacts can be generated:

  1. Squishy / sparkly high pitches (10-20kHz), probably aliasing reflections but sometimes extending quite low
  2. Very high harmonics above the input pitch (strongest at 512th partial = 9th octave) which swell in the peaks & troughs of the waveform
  3. Static whine sound (at roughly samplerate / 64) when the waveform crosses the 0 line at a certain rate

I made a video demonstrating these sounds here: https://vimeo.com/396514538

I’m not a DSP wiz so I don’t know to what extent artifacts like these are expected, but I’m very curious what someone more knowledgeable in this area would have to say. Happy to provide the patch if anyone wants.

5 Likes

I love that you’ve not only found these complex sounds in such simple DSP, but documented them so thoroughly. That video is wonderful.

If pd’s osc~ works like Max’s cycle~, then it isn’t really outputting a mathematically pure sine wave, but reading from a 512-sample single-cycle wavetable. I’ll bet that at least some of the artifacts you’re hearing – especially the loud 512th harmonic – are related to that, and are only made audible by the HPF, not caused by it. You may be able to get a purer sub-audio cosine signal using a combination of phasor~ and cos~.

The increased artifacts you’re getting when you add a DC offset, though – I won’t try to offer a theory there. Hopefully someone who knows more about filter design can explain that.

3 Likes

yep, this is the case in puredata’s osc~ also, and it uses linear interpolation. so i concur that this stuff is mostly or entirely attributable to interpolation error. in a nutshell, these errors will produce more or fewer odd-order distortion harmonics depending on the relationship between per-sample phase increment and table size (so they will sweep around in pitch, amplitude, and distribution as you move the cos freq), and those harmonics will alias.

(did you try just looking at the super-low osc~ without the HPF?)

the code for the production and consumption of the cosine table is a little odd, since it kinda abuses the bit layout of 32-bit IEEE floats for efficient interpolation (? haven’t quite grokked it tbh. and given that, i can’t really rule out an error - maybe worth noting what kind of processor you’re using. also suspicious that there seems to be more error at the extremes of the cos waveform - the bounds of the lookup table.)

tab size defined: https://github.com/pure-data/pure-data/blob/master/src/m_pd.h#L620
tab computed: https://github.com/pure-data/pure-data/blob/master/src/d_osc.c#L204
lookup: https://github.com/pure-data/pure-data/blob/master/src/d_osc.c#L263

i’m pretty sure cos~ uses the same table, just applying it as a transfer function instead of looping over it. somehow not spotting that code right now.

so if you want to try a higher-quality (and slower) cos approximation, might be best with a simple custom object (store phase increment and phase as double, and just skip the table and use standard cos() function.)

you can also generate a much cleaner sine with a self-oscillating 2-pole filter, as is done in supercollider’s FSinOsc Ugen. (with important caveat that the amplitude falls off at low frequencies, IIRC? and it can blow up if you modulate it with big discontinuities.)
source for that, which you could adapt to pd’s raw filter interfaces (too lazy to look for it online r/n):

void FSinOsc_Ctor(FSinOsc* unit) {
    if (INRATE(0) == calc_ScalarRate)
        SETCALC(FSinOsc_next_i);
    else
        SETCALC(FSinOsc_next);
    unit->m_freq = ZIN0(0);
    float iphase = ZIN0(1);
    float w = unit->m_freq * unit->mRate->mRadiansPerSample;
    unit->m_b1 = 2. * cos(w);
    unit->m_y1 = sin(iphase);
    unit->m_y2 = sin(iphase - w);

    ZOUT0(0) = unit->m_y1;
}

void FSinOsc_next(FSinOsc* unit, int inNumSamples) {
    float* out = ZOUT(0);
    double freq = ZIN0(0);
    double b1;
    if (freq != unit->m_freq) {
        unit->m_freq = freq;
        double w = freq * unit->mRate->mRadiansPerSample;
        unit->m_b1 = b1 = 2.f * cos(w);
    } else {
        b1 = unit->m_b1;
    }
    double y0;
    double y1 = unit->m_y1;
    double y2 = unit->m_y2;
    // Print("y %g %g  b1 %g\n", y1, y2, b1);
    // Print("%d %d\n", unit->mRate->mFilterLoops, unit->mRate->mFilterRemain);
    LOOP(unit->mRate->mFilterLoops, ZXP(out) = y0 = b1 * y1 - y2; ZXP(out) = y2 = b1 * y0 - y1;
         ZXP(out) = y1 = b1 * y2 - y0;);
    LOOP(unit->mRate->mFilterRemain, ZXP(out) = y0 = b1 * y1 - y2; y2 = y1; y1 = y0;);
    // Print("y %g %g  b1 %g\n", y1, y2, b1);
    unit->m_y1 = y1;
    unit->m_y2 = y2;
}

also maybe worth noting that hip~ appears to just have a single pole, producing a -6db/oct rolloff that will not reject DC very well at low cutoff freqs:
hip~ source: https://github.com/pure-data/pure-data/blob/master/src/d_filter.c#L55

and i think some of the artefacts could arise from the way pd zaps denormals in the filter history (again, worth noting your processor arch) - in particular i’m suspicious of these when it comes to the relatively loud whine at subharmonics of the samplerate: when the input is crossing zero, “potential future denormals” are entering the filter history, and maybe getting too aggressively zapped every other sample (or whatever.)

i would try just commenting out lines 103, 104 in d_filters.c to see what happens? (zapping denormals is an optimization, not supposed to affect accuracy, but my gut feeling is it’s kinda bad practice to deal with them by testing bits instead of using the processor’s own flush-to-zero instructions.)

m_pd.h
748:static inline int PD_BIGORSMALL(t_float f)  /* exponent outside (-64,64) */
771:static inline int PD_BIGORSMALL(t_float f)  /* exponent outside (-512,512) */
781:#define PD_BIGORSMALL(f) 0
789:#define PD_BIGORSMALL(f) ((((*(unsigned int*)&(f))&0x60000000)==0) || \
793:#define PD_BIGORSMALL(f) ((f) > 1e150 || (f) <  -1e150 \

anyways, interpolation and aliasing artefacts down around -80db aren’t exactly surprising with the kinds of corners that are often cut for efficiency in computer music systems.

but yeah, i too enjoyed hearing them in that video and the general exploration of the microscopic glitch landscape! (though i don’t know that i’d describe even these basic DSP blocks as exactly “simple” when you get down to the details.)

3 Likes

wow, thanks for the insightful responses @zebra and @synthetivv! I had no idea that the osc~ object itself would be a wavetable, but it does make sense that it would be and totally checks out with the coincidental 512th harmonic.

I’ll certainly try some of the purer cosine ideas you’ve suggested. My C chops are pretty rusty but it would be a fun learning experience to write some kind of heavy-cos~ object.

I think I did try the super-low osc~ without hip~ and couldn’t hear the interpolation errors, but I should double check. Funny enough, I was using 2048-sample wavetables with tabosc4~ in earlier instances of this patch and getting even more noticeable interpolation noises – including end-of-cycle clicks – and switched to plain osc~ to avoid the clicks.

For the moment my primary interest is understanding these artifacts for use in a piece. I don’t know very much about the signal math behind 1- or 2-pole filters and how they interact with DC offsets (beyond what I’ve learned through empirical exploration). I’m interested in potentially building my own abstraction or external for it though, as I haven’t found a solution that allows transitioning the filter threshold at signal rate, so I’m going to try to dig into it a bit eventually.

Will have to try removing those denormals optimizations, but that will be a weekend thing, I think.

Glad you both enjoyed the video, and thanks for the homework!

That sounds like probly table in slightly wrong format (e.g. should be 2049 values with one repeated, I dunno)

The DC thing is weird and I’m gonna maybe look at it closer in a model

That’s also what I thought, but if you set the table to a non power-of-2 length, pd says it’s truncated, and the documentation for tabosc4~ / tabread4~ say that 4-point interpolation is taken care of behind the scenes (possibly only in combo with sinesum, which is what I was using anyway…)

1 Like

I’ve been working more and more with PD, mostly with Automatonism but I’m trying to branch out more and integrate/learn more ground up PD in combination. I’m wondering if there is a way around this issue I’ve run into, maybe the solution is kinda obvious to someone with more straight up PD experience…

So say I have a patch built up from Automatonism modules, but I want/need to make changes to a particular instance of a module - how can I save it so I don’t have to make the change each time I relaunch PD without it also overwriting the other instances of that module?
My example- I have 2 instances of the external audio module because I want to use 4 inputs. So, I open one and change the adc~ 1 2 to adc~ 3 4. But then when I quit I can either save the change, making both instances adc~ 3 4 next time I open, or have to make the change each time I reopen. While in this case I can work around by using 4 envelope followers and adc~ I imagine similar instances will come up. Is there a simple workaround I’m not understanding, or should I rather just create my own subpatches as I find mods necessary to Automatonism?

Also somewhat related, if anyone has had a look at the looper module, is there a few simple parameters I could change to make the recording times longer/shorter, or would it be involved enough I might as well build my own adjustable looper from scratch?

If you have a soundcard that supports multiple inputs, say 4, then [adc~ 1 2 3 4] should work. You’d need to account for this in Audio Settings as well, by setting four input channels.

1 Like

yeah thats what I’ve done for now. its more how to find a solution for this in terms of being able to save modifications to modules for instances where I can’t make a substitution like this.

New video demonstrating the hip~ artifacts using a “raw” oscillator: https://vimeo.com/397799230 (as well as attempting to summarize @synthetivv and @zebra’s insights.) Meant the video to be shorter but time got away from me. Demonstration starts around 4:50.

The interpolation error idea is correct; a pure cosine does not produce the 512th & related harmonics, so these artifacts must result from the wavetable combined with the high-pass (since they’re not audible with just osc~ at low frequencies). The “squishy” sounds changed in character as well – notably, the pitches making up the arpeggio-like patterns when driven by osc~ become continuous “glissandi” with rawosc~.

The samplerate / 64 whine isn’t affected by the change. I haven’t gotten around to building a patched pure data without the denormals optimization.

2 Likes

Hi all, super greenhorn PD user here. I am experimenting sending messages to ORCA via UDP and have had some success but when I try to send a “select:3;4;;” in a message it reads the ; as newlines in the message and it doesn’t send correctly. I know there is probably some syntax for this that I’m not getting or I need to convert it to a string somehow before I send it as a message?

If I understand what you’re trying to do, you’ll want to convert the text to a symbol or else it’ll be parsed as a native pd message would be. The way to do this with strings I’ve found is to create a text define, put your command into it, and send 0 (the line index) to a text get to output the text. It would look something like this:

[text define msg]

[0]|[text get msg]|[symbol $1]|[send /msgName $1]|[packOSC]|[udpsend]

it randomly occurred to me to mention that you could have a much more effective dc blocker with 1 pole and 1 zero

c++ code like so. (could be decomposed into arguments to rzero~ and rpole~, i think)

// 1-pole, 1-zero highpass, suitable for cheap DC blocking

namespace dspkit {
    template<typename T>
    class DcBlocker {
        T c; // coefficient (pole position)
        T x1; // input history
        T y1; // output history

    public:
        // initialize with sample rate and -3db break frequency
        void init(T sr, float fb = 30) {
            static constexpr double pi = 3.1415926535898;
            double wn = pi * fb / sr;
            double b0 = 1.0 / (1.0 + wn);
            c = (1 - wn) * b0;
            x1 = 0;
            y1 = 0;
        }

        T processSample(T x) {
            T y = x - x1 + c * y1;
            x1 = x;
            y1 = y;
            return y;
        }
    };
}
1 Like

Thanks for trying to help but I’m still not getting it. If you have time could you patch up an example so I can see what you’re talking about? Are you banging the message into [text define]? It still throws errors because of the broken lines.

So what I do to get around this is just copy the initial blank unzipped automationism folder, and dupe it every time I want to create a new patch. That way you are free to mod the abstractions as required for each patch and you can always just start from a blank project for a new patch.

In your case, I would just copy the abstraction and rename. I think there might be a few other small edits required as well for preset saving on the edited abstraction.

1 Like

I think you are on the right track here but I’m guessing this needs to be done in a particular order/way for it to work. I tried opening a patch, adding two instances of audio input, changing one to [dac~ 3 4], saving it with a different name, but as soon as I save PD crashes. The new object exists in the folder, but of course it doesn’t show up on the modules list, and it doesn’t show up as the altered or renamed object when I re-open the patch. I’m guessing it has something to do with how these objects are made and creating a new one just be renaming something and not changing some other references or lists somewhere is making things crash.