• ddw_music

    @alexandros said:

    Am I missing something, or the patch below isn't correct?

    It's correct if you use the right outlet instead of the left outlet of threshold~.

    @_ish

    supercollider in the early 2000s

    SC person here!

    ignored PD for the most part under an impression it lacked refinement

    Just the other day, I already did the rant about how Pd's 1990s-style GUI gives people the wrong impression about its capabilities, and how PlugData is a good way forward that deserves more support... so I'll stop there.

    Pd is solid. What's lacking in the built-in feature set is convenience. For example, if you load a soundfile... how long is it? What is its sample rate? In SC, you have BufFrames, BufDur, BufSampleRate, BufRateScale. Pd does spit this information out of soundfiler at the moment of loading, but if you didn't retain those values at that time, then they're gone. So with SC, I feel like it's more straightforward to get into it incrementally -- you're hacking away, and then you find that you need the buffer's sample rate, no problem! It's right there. In Pd, at minimum, you'll have to issue a dummy read command to soundfiler, and unpack the list.

    I got annoyed enough about this that I created some abstractions to help deal with soundfiles: https://github.com/jamshark70/hjh-abs :

    pd-bufs.png

    [monofile] and [stereofile] for basic reading. Mono files play by sf-play~ or sf-varispeed~; stereo by sf-play2~ and sf-varispeed2~. Rate = 1.0 is always the file's normal speed. Also it creates [value] vars for buffer stats, and I have a read-only [getvalue] abstraction to access them without risk of overwriting.

    ... Strictly speaking, you don't "need" these -- they're only using information that is available in vanilla anyway -- but I just don't think users should have to deal with these fiddly details routinely. So it's not a matter of lacking refinement, but rather that it takes more work to rearchitect features that you might take for granted in other platforms.

    I suppose it depends on the task, but in most cases, I can get there faster in SC; the threshold of complexity that I'm willing to attempt in SC is higher than that in Pd. (I usually find patching to be more cumbersome than code.)

    hjh

    posted in technical issues read more
  • ddw_music

    @_ish said:

    1. there used to be a random object that spit floats (randomF).

    The ELSE library has [rand.f].

    (I wish, when I started with Pd, that someone had pointed me to ELSE... a lot of the things that are missing from vanilla, or "build it yourself," are just there.)

    1. Earlier I was working on an array, and really wanted to send a bang every time it looped around to index 0. This feels like it should be really easy, but I couldn't think of anything that would send the active index step as out (the contents of the index step, sure, but not the step number itself).

    Are you reading the array in the control or signal domain?

    whale-av:

    As in that link if using [phasor~] catching it's output as it passes 0 is unreliable because the value of [phasor~] will likely not be 0 as it is captured at a block boundary.

    The value of phasor~ isn't reliable for this, but the two-point difference ([rzero~ 1]) is guaranteed to be positive most of the time, and negative only when the phasor jumps down. So this will always work (for a positive phasor~ frequency).

    pd-phasor-reset-trig.png

    hjh

    posted in technical issues read more
  • ddw_music

    @ddw_music said:

    At this point, there are a couple of differences from the classic Pd GUI. One is that the graph-on-parent area is locked to the top left corner -- origin is always (0, 0). That's a limitation, but not an outrageous one.

    FWIW -- I rechecked and this is not true. You can move the top left corner by mouse.

    @whale-av "Did I just lose an eyebrow?" :laughing:

    hjh

    posted in technical issues read more
  • ddw_music

    @_ish FWIW... in PlugData (a JUCE wrapper around Pd, which can run as a plugin or standalone), you can:

    1. Create a [pd something] box and open it for editing.
    2. Right-click in an empty area of the canvas --> Properties.
    3. Set "Is graph" = Yes.

    At this point, there are a couple of differences from the classic Pd GUI. One is that the graph-on-parent area is locked to the top left corner -- origin is always (0, 0). That's a limitation, but not an outrageous one. The other is that you can grab the lower right corner and resize by mouse -- both in the subpatch window and in the parent window. (In the PlugData version installed on my machine, however, there is a UI inconsistency -- the canvas properties panel shows width and height, but the numbers here do not sync up with the size that you set by mouse -- in fact, they seem to have no effect at all. That's obviously a bug; I'll report it later.)

    Should I do the rant? I kind of feel like doing the rant.

    The classic Pd GUI was designed in the mid-90s, and it looks like it, and it acts like it. It's not going to improve. You'll get people on this forum telling you that it doesn't need to improve, because they've been using it for a long time and they're used to it. As an opinion, that's fair enough, but being used to it doesn't negate the observation that this GUI has been sleeping through three decades of UI standards development (and it contradicts those UI standards in some areas -- "no GOP resize by mouse" is one -- the weird behavior of entering edit mode after moving an object by mouse is another).

    This GUI is holding Pd back. I've had students tell me, when they see the classic GUI, basically... "Uh. Just no." They won't touch it. They don't care that it's nice and comfy and familiar for old-guard users on the forum. For them, this is not how software is supposed to look.

    PlugData is a much-needed shot in the arm, to keep Pd going for a few more decades and attract users who would otherwise look at the chunky black-and-white non-anti-aliased* UI (edit: I forgot about only monospaced fonts in object boxes!) and think, "Why are these people still stuck in 1996?"

    * (IIRC Tcl/Tk line drawing is anti-aliased on Mac but it isn't on Windows or Linux. But even suggesting this really basic UI improvement can be controversial on this forum. Few years back, I saw someone on here say that anti-aliased diagonal lines are "too smudgy," preferring stair-stepped pixels because they're "sharp." If that's the climate, then the only way to bring Pd's UI into the modern era is for somebody just do it... which Timothy Schoen did.)

    Speaking of being stuck in the 90s, I'll now say "flame suit on" :laughing:

    Anyway, do try PlugData. I use it routinely, pretty much only using the classic GUI if I found a bug and somebody asks, "Did you reproduce it in vanilla?"

    hjh

    posted in technical issues read more
  • ddw_music

    @willblackhurst said:

    and then you ask for numbers from the array with tabread will give you one number from the list. which they call a sample. etc sample rate...

    In context of the question, this is not quite revelant.

    First, it's about audio signals -- not [tabread], but rather [tabread~] or [tabread4~].

    Second, audio objects don't operate sample by sample, but rather block by block.

    The question doesn't state it explicitly, but it can be inferred from context that the "single-sample operation" being referred to is single-sample feedback. Feedback always requires delay, and, using normal audio objects, the minimum delay is the block size. This places limits on the capability of implementing filters, Karplus-Strong plucked strings (this is one of the OP's keywords), waveguides etc.

    @ardore In Pd, AFAIK pretty much all you've got is to set a subpatch's [block~] settings to block size = 1. Then that part of the graph will run everything by single samples (but outside the subpatch / abstraction window will run with normal block size).

    I'm not aware of anything gen~ like in Pd (which isn't surprising, since David Zicarelli says it took their team of paid professional developers something like 6 years before gen~ was ready to ship -- an unpaid FLOSS team is unlikely to be able to duplicate that engineering effort). There might be something that I just didn't hear of...? But I doubt it.

    Something that Pd devs might consider is to leverage another similar technology. For instance, a SC contributor released "DynGen" a few months ago, which wraps Reaper's audio-fx dev language "eelscript" into a SC unit generator. Eelscript can do a lot of gen~-like things, and... Reaper devs did the hard work! Might be interesting to have a Pd signal-object wrapper for eelscript...

    hjh

    posted in technical issues read more
  • ddw_music

    FWIW, in abstractions I generally avoid the [f $1], [symbol $2] type of usage because now the user is obligated to supply a value for every dollary-thingy -- the abstraction can't supply a sensible default.

    Instead, I do [pdcontrol] --> [pack].

    pd-args.png

    ... and if I create this abstraction with no object box args, it prints:

    arg-values-from-f-box: 0 0     <<--- I usually don't want this
    arg-values-from-pack: 100 200  <<-- more useful
    

    ... but if you supply values, the arg list from pdcontrol overwrites defaults in [pack].

    The inlet~ default thing is great! And very hard to do in Max/MSP (actually can't be done 100% reliably). Which is one place where I often tell students, "Pure Data is where Miller Puckette learned from Max's mistakes."

    hjh

    posted in technical issues read more
  • ddw_music

    I've also heard that 4th-order filters are difficult to implement when using single-precision floats because 4th-order recursive filters require higher numeric precision to avoid blowups. Cascading two identical 2nd-order filters gets the 24 dB/oct slope while staying well within the limits of single precision.

    hjh

    posted in technical issues read more
  • ddw_music

    @solipp said:

    use [set 64 1 $1(
    no need to switch dsp off

    Thanks -- in the reference, I saw "<list>" but I missed the "set" before it. Good catch.

    Yeah, it's working now!

    hjh

    posted in technical issues read more
  • ddw_music

    03-oversampling.pd

    I was writing up a quick demo of oversampling, and found to my surprise that [block~] seems to be ignoring messages. I've got a toggle going into the subpatch, [inlet] --> [* 7] --> [+ 1] --> "64 1 $1" --> [block~], and when I flip the toggle, there's no difference in the sound.

    I even tried switching dsp off and on again, but no dice.

    Are messages to [block~] supposed to work? Or, should they be sent only when dsp is already off?

    Here's another version of the patch that switches DSP off, sends the block~ message, then switches back on. I don't hear it doing anything. OK, worst case I'll just have two oscillators and switch them at the output, but the block~ help patch documents that a message can be used and AFAICS it doesn't work.

    03-oversampling-b.pd

    TIA,
    hjh

    posted in technical issues read more
  • ddw_music

    @Moddmo said:

    I guess a better question is if pd has disadvantages here compared to other DSP programming languages.

    Granular synthesis is sometimes imagined as this magical, complex thing, but the fundamentals are quite simple. Get the fundamentals right, and the sound quality follows from that (and is mainly a matter of parameter tuning).

    So you're playing back a block of audio from a buffer or a delay line. (I used a delay line because that's the best way to implement a circular buffer in Pd vanilla. You could also use cyclone [count~] to generate phase for use with [tabwrite~] and [tabread4~].) The important points here are to get the boundaries of the audio segment correct, and to modulate the starting position intelligently. "Boundaries" includes concepts of: how many grains per second should be triggered, how many of them overlap, how fast will the audio be played.

    And each grain needs an envelope matching the grain duration.

    In my example, I've already tuned it for one specific use case (pitch shifting). But there's no magic here -- it really is just linear audio playback with envelopes, overlapped and added.

    Comparison with other DSP environments, then, is just a matter of implementation. E.g., SuperCollider has UGens (single objects) TGrains and GrainBuf that do the audio segment and envelope and overlap/add for you, so that you can concentrate on the parameters:

    s.boot;  // sort of like "; pd dsp 1"
    
    (
    var rateSl;
    
    a = { |inbus, rate = 1, trigFreq = 66.66667, overlap = 4|
        var sr = SampleRate.ir;
        var maxDelaySamps = 2 * sr;
        
        var src = In.ar(inbus, 1);
        
        // "delwrite" part (rolling my own circular buffer)
        var buf = LocalBuf(maxDelaySamps, 1).clear;
        var phase = Phasor.ar(0, 1, 0, maxDelaySamps);
        var writer = BufWr.ar(src, buf, phase);
        
        // *all* of the rest of it
        var trig = Impulse.ar(trigFreq);
        var grainDur = overlap / trigFreq;
        var delayBound = max(0, grainDur * (rate - 1));
        // grain position in samples, for now
        var pos = phase - (sr * (delayBound + TRand.ar(0.0, 0.003, trig)));
    
        GrainBuf.ar(2,
            trig, grainDur, buf,
            rate,
            pos: (pos / maxDelaySamps) % 1.0,  // normalized pos in GrainBuf
            interp: 4  // cubic
        ) * 0.4;
    }.play(args: [inbus: s.options.numOutputBusChannels]);
    
    rateSl = Slider(nil, Rect(800, 200, 200, 50))
    .value_(0.5)
    .orientation_(\horizontal)
    .action_({ |view|
        a.set(\rate, view.value.linexp(0, 1, 0.25, 4))
    })
    .onClose_({ a.free })
    .front
    )
    

    The DSP design here is the same as in the Pd patch (rate-scaled audio segments under Hann windows [GrainBuf gives you Hann windows for free], with a 3 ms randomized timing offset for each grain) so the sound should be basically identical. Personally I find the SC way to be easier to read and write, but I wouldn't expect everyone on a graphical patching forum to feel the same :wink:

    hjh

    posted in technical issues read more
  • ddw_music

    OK, here's a basic live input granulator, no really fancy features, just pitch shifting.

    The handling of grain playback rate in the one-grain abstraction is a neat trick I had worked out some time ago. For example, if you want to play the grain an octave higher (2x speed), then you need to span 2 * dur ms of audio in dur ms of time. You can do that by modulating the delay time as a straight line, from dur ms to 0 ms -- adding dur to the delay time at the beginning adds that many ms to the amount of audio being used: dur * (rate-1). Or, to play slower, start with a shorter delay and go toward a longer delay, and the boundary is again dur * (rate-1). If rate = 1, then the delay time goes from 0 to 0 = no change = normal speed. That might look a bit funky in the patch you can try it with different transposition intervals, which will show that it's correct.

    For sound file processing, replace the delay line with a soundfiler-filled array, and use tabread4~ for the audio source (and the line~ driving it will have to be different too).

    IMO granular processing is 99% refinement and more advanced modulation of these basic parameters, which you should be able to tailor to your needs. I think the pitch shifting is more-or-less smooth here, though I'm not sure it matches your comparison plugins -- this is 66.6667 grains per second, with 4x overlap.

    one-delay-grain.pd
    live-granular.pd

    hjh

    posted in technical issues read more
  • ddw_music

    @Moddmo said:

    I don't want to spend months making a patch and end up with crap sound.

    Well, that's hard to promise because I'm not sure exactly what you mean by crap sound. :laughing:

    What I can say is that granular synthesis is made up of short clips of audio under envelopes. Pd can do both:

    • clips: tabread4~ for a sound file loaded into memory, delread4~ for live input.
    • envelopes: you can fill an array with a Hann (or any other type of) window, and stream it out using tabread4~ as well.

    Pd has one edge over Max here, in that Pd's metro is sub-block accurate. In both Pd and Max, there's an audio block size (default 64 samples), and control messages execute only on those block boundaries. In Max, last time I tried to do granular synthesis driven by control messages, I could hear the timing inaccuracy due to the messages being quantized to these block boundaries. (Maybe that's changed in Max 9, but that's my recollection from Max 8.) In Pd, control messages are processed on block boundaries, but they also carry sub-block timing information so that grains will start on the right sample, not just the right block. IMO Pd's timing was noticeably smoother. (In Max, multichannel signals get a better result.)

    For sound quality, it's very helpful to introduce a little bit of randomness into the grain time position, to avoid "machine gun" effects.

    Again, "close to the available software," I'm not sure exactly what you mean. With proper tuning, I was able to get a pretty smooth sound out of it. Maybe an example later.

    hjh

    posted in technical issues read more
  • ddw_music

    @jamcultur said:

    If the code on Windows was the same as the code on Mac, they would work the same.

    One source of confusion here is the difference between source and object code.

    Most of the time, humans never look at the object code produced by a compiler. We only look at the source code. So, when porres says there's no difference in the code between platforms, this is talking about source code.

    The source code gets compiled into object code. In Mac vs Windows, the compilers are different, and the CPUs (architectures and instruction sets) are different. If the Mac is using an M-series CPU, then it's impossible for the object code to be the same as Windows (Intel or AMD chip), because the instruction sets are completely different. (That's also not considering the differences in OS function calls, which of course will not be the same between different OSes.) So in fact, "the code" isn't the same -- but this isn't porres's fault, and there's no way for the code not to be different.

    Ideally, the same source code compiled for different chips should produce equivalent results. Programmers usually take this as a safe assumption. But there are edge cases where it might not work out that way (we just saw one of those over in SC-land, related to floating-point rounding). These cases can be extremely difficult to debug, and at the end of the day, one is at the mercy of the CPU and the compiler's behavior.

    In such cases, it isn't helpful to accuse a developer of writing different code for Windows (this is implausible for DSP code in any case, which is mostly math operations that are well abstracted -- you don't need #ifdefs for std::xxx() math functions) or of "not caring" enough.

    We want to assume that the compiler and CPU are transparent with respect to the source code's meaning. When that isn't the case, it's necessary to inspect every operation. It's painful, and if porres doesn't have access to a machine where the problem occurs, it can be very slow (test builds, relying on other people to run the specific tests). A little patience goes a long way.

    hjh

    posted in technical issues read more
  • ddw_music

    @porres said:

    cyclone does not have a [spectrogram~] object

    ELSE has [spectrograpg~] though.

    Thanks -- I didn't look closely enough at the help patch.

    About formant synthesis with FM, I know Miller includes something like that in the audio examples (see F10). Is it related maybe?

    It probably is, and his implementation is probably more elegant than mine (though it's jammed into a small space on the screen so it's a bit tough for me to read quickly).

    It takes some tuning -- the FM index isn't a simple analog to formant bandwidth (it seems to need to be scaled down at higher pitches). But it's computationally cheap and gets a useful result, and it seemed to fit "approaches to formants other than bandpass filtering."

    hjh

    posted in technical issues read more
  • ddw_music

    There's also the John Chowning "Phoné" FM formant approach, where the carrier is at the formant center frequency and the modulator is at the fundamental. It's "formant-ish" I suppose, but sweeping the fundamental while holding the formant frequency steady does produce a vocal-ish sound.

    Here I'm crossfading between two formants, to make smooth transitions between integer carrier-mod frequency ratios.

    pd-fm-formant.png

    formant-fm.pd

    Oops, no: (~[spectrogram~] is from cyclone -- not essential to the patch's operation.)

    (~[spectrograph~] is from ELSE -- not essential to the patch's operation.)

    hjh

    posted in technical issues read more
  • ddw_music

    @willblackhurst Tilde objects don't have anything to do with voltage inside the computer.

    hjh

    posted in technical issues read more
  • ddw_music

    Interesting that both responses assumed that atux wants to send pitch bend messages out, but "modulate the pitch in real time by moving a slider" says nothing about MIDI being the target.

    The question might just as easily be, "How to map a slider onto a frequency ratio, to multiply with the note's main frequency?"

    Normalize the slider value. Because pitch bend normally goes both up and down, I normalize to -1 .. +1.

    line~ for smoothing. (Also, I'm stealing the mouse-release logic from porres, good tip!)

    Pitch bend range is given in semitones. We need a fraction of an octave = pbrange / 12.

    Scale the normalized pb value onto the fraction of the octave = [*~].

    The ratio for one octave = 2. So, the ratio for the fraction of the octave = 2 ^ fraction.

    Now you have a ratio that you can multiply by any frequency, and get pb.

    pd-pitch-bend.png

    (More generally, almost any kind of exponential modulation i.e. frequency can be expressed as baselineValue * (modRatio ** modulator). Pitch bend is a specific case of this, where modRatio = 2 and the modulator is scaled to the range +-pbrange / 12. Linear modulation just demotes the math ops: baselineValue + (modFactor * modulator). With these 2 formulas you can handle a large majority of modulation scenarios.)

    hjh

    posted in technical issues read more
  • ddw_music

    @fer FWIW this is a formula I've used for abstraction init defaults for awhile now -- only vanilla objects, no externals needed.

    pd-abstraction-defaults.png

    If the usage of the object is [defaults-test] with no args specified, it prints abstraction_defaults: list default symbols 0 1 2 3 = the values that I put into the [pack].

    If it's [defaults-test xyz ttt 34 72], then it prints abstraction_defaults: list xyz ttt 34 72 2 3 where the 4 values overwrote the [pack] values from the start... generally what you'd assume from defaults.

    (If all the args are numbers, then you don't need any funny business, just [pdcontrol] --> [pack].)

    hjh

    posted in technical issues read more
  • ddw_music

    you did spread misinformation about it

    So just correct it and move on.

    When people try to reinvent what already exists... I built this for the community. Ignoring existing tools and efforts misses the spirit of open source

    Both Pd and SC have a systemic problem wherein there is no good way for new users to know which extensions exist. Recent versions of Deken improve the situation somewhat for Pd, and there's a similar effort underway for SC, but "missing the spirit of open source" is quite a burden to lay on somebody who might have using the tool for just a couple of weeks or months.

    So I'm out of this thread. I like a lot of the stuff in else, really, and I wish I'd known about it from the start. (Btw "when it's there in plugdata already" -- when I started using Pd in classes, there was no plugdata and there was no pd-extended, and no way to discover ELSE by chance.)

    hjh

    posted in technical issues read more
  • ddw_music

    @porres said:

    nah, I'll just leave as it is, the object is already too much complicated and I don't know how to deal with it (if anyone has a suggestion, please let me know).

    Maybe like this? Instead of velocity --> envelope, derive a gate by way of [change]. Then multiply the envelope by the velocity value. The volume will change if the velocity changes on a slurred note. If you don't want that, it should be possible to close the spigot when slurring to a note, and open it only when a brand-new note is being played.

    pd-mono-midi-else-2.png

    hjh

    posted in technical issues read more
Internal error.

Oops! Looks like something went wrong!