Miscellaneous Findings IX: Pure Data and JUCE

This is a roundup of miscellaneous things that I’ve found out about (or have rediscovered). I take notes on findings regularly, and I put the findings that translate well to speech on my podcast, Small Findings. The rest (which are often technical findings), I put here. They’re not always written up for maximum comprehension as a blog post, but if anything is hard to understand, please email me if you need clarification.

During the past week at the Recurse Center, I started learning how to use Pure Data, a music and signal processing language, and JUCE, a framework for making audio software. In the process, I stepped on many metaphorical rakes. Perhaps, by reading this, you can step around them.

Sideshow Bob steps on rakes

Pure Data

I started using Pure Data. Here’s some mysteries and problems I’ve run into as a beginner so far, as well as how I got around them.

Installing

Installing the vanilla distribution on Debian

You could do:

    sudo apt-get install puredata

Then, you can start it with pd.

But I ended up with something that wouldn’t play sound when I did it this way, though you may have more luck.

Installing the purr-data distribution

So, I tried this distribution of Pure Data, which started as Virginia Tech Laptop Orchestra’s distribution.

From https://github.com/agraef/purr-data/wiki/Installation#linux:

    sudo su

    wget -nv https://download.opensuse.org/repositories/home:aggraef/xUbuntu_20.04/Release.key
    apt-key add Release.key

    echo 'deb http://download.opensuse.org/repositories/home:/aggraef/xUbuntu_20.04/ /' > /etc/apt/sources.list.d/home:aggraef.list
    apt update

    apt install purr-data

Then, I followed these steps:

  • Start Jack by hitting start in QJackCtl.
  • Open Pure Data with pd.
  • Under the Media menu, choose Jack.
  • Click DSP On under Media.
  • Click Test Audio and MIDI under Media.
    • It will load a “patch” (an audio graph).
  • Click 80, under Test Tones in the patch.

A sound played!

It Worked for Me (TM); I hope it does the same for you.

Making a patch (Pure Data’s name for a program) run

In Pure Data, go to Edit | Edit Mode and uncheck it to put it into Run mode. Now when you click a message, it will actually be sent to whatever box it’s connected to.

Sometimes when an example patch doesn’t play, there’s a volume somewhere in the graph that you need to turn up.

Sometimes, you outright have to add a dac~ object (Digital audio converter. You need it to play signals. There’s not a good link that explains just that object, it seems.)

Connecting objects

Click on one of the tiny black rectangles at the bottom of the sending object (even though, confusingly, it will have the “you can’t do that” mouse cursor), then drag to the receiving object.

The black rectangles at the bottom of the object are called outlets, and they send things out of the object to whatever you connect them to.

The black rectangles at the top of the object are called inlets, and they take things into the object from whatever you connect them to.

Some objects I’ve noticed

tabreceive~

This takes an array (via an argument, not from an inlet) and emits blocks of samples from it (via an outlet). The size of the block seems to come from the array.

~ what does it mean: The tilde ~ indicates an object that works on a signal (a stream of samples) rather than a single value.

bang objects are buttons.

You click them and they send a message out. I’m clearly not Miller Puckette, the creator of Pure Data, but if I were, I would have named these buttons.

s and where is the object reference?!

s is an abbrevation for send. But I had a hard time finding that out.

I searched fairly extensively and couldn’t turn anything up. It was not in the help or the manual that ships with Pure Data. So, I asked a question on the Recurse Center internal message boards.

My friend Edith found this pdf about using Pure Data on the iPad for me, which explained that s was an abbreviation.

Later, on the Discord, I asked where the definitie object reference was and was told you can just right-click on an object, then get a help topic about it! This includes abbreviations! That is where the definitive object reference is.

Handy, yet hidden. Sure, some of this problem could have been alleviated by me reading more carefully and considering more search results. But I think there is a real discoverability problem here.

Popping noises

When you play a plain oscillator in Pure Data, should it make popping noises? Because I got popping noises.

No, it should not. I listened to this video to hear what a normal Pure Data sounded like.

After poking at a lot of Pure Data audio settings, and a lot of searching, I considered the possibility that it was a problem with Jack. I posted a recording of what I was hearing and a request for ideas to an internal Recurse message board.

Joe, another Recurse friend, reported that the recording sounded fine.

I turned off Jack and listened back to the recording.

It did sound fine. There was no popping. It sounded like a pure cosine wave. I realized Pure Data itself wasn’t the problem.

I read more about Jack and eventually figured out that, while Jack lets you set any sample rate you want in its settings, the one that shows up in the “current sample rate” in QJackCtl is apparently the only one Jack can actually successfully play at. The current sample rate was 48 kHz, but I had it set to 44.1 kHz to match the default sample rate in Pure Data.

Once I changed the sample rate to 48 in Jack and Pure Data settings, the pops were gone.

In retrospect, I can imagine why the popping happened. If Pure Data sends 44,100 samples to Jack every second in 512-sample blocks, Pure Data expects Jack to take 11.6 millisecords to play each block (512/44100). If Jack is actually trying to play 48000 every second, it will finish playing each block in 10.67 milliseconds (512/48000).

What does Jack do with the leftover time (1.07 milliseconds)? My guess is that it fills it with silence, but it could also be filled with random noise. Either way, whatever it played was probably a big jump away from the last sample in the block sent by Pure Data. When there is big jump from one sample to the next in an audio signal, you’ll hear a pop.

Reading audio files

I used this blog post to learn how to read audio files in Pure Data.

Wav file sample rates

pd can’t seem to handle wav files that have a different sample rate from what’s in the pd audio settings. So, I just converted by wav file sample rates (sox in.wav -r 48000 out.wav).

Tables are global

If you get errors like:

warning: infoFile: multiply defined warning: carrierFile: multiply defined

It is because you have a table with that name defined in multiple patches that are currenly open. Tables are global across patches. Tables are subpatches containing an array and a graph, and subpatches must be uniquely named across…the universe.

If you have two patches open that each have a table named infoFile in them, objects that reference infoFile can actually access the wrong infoFile.

I’m guessing there is some conventional way to avoid collisions, though, because people aren’t naming their tables with UUIDs.

#puredata #global #docs #jack

JUCE

JUCE is a commercial framework (they charge you money if you make money) for audio software.

Building a Projucer-created project

Projucer is a scaffolding tool that sets up a project for you that includes a (usually) working build configuration and whatever boilerplate you need.

Getting missing packages

If you get something like Package libcurl was not found in the pkg-config search path. when you build a project created by Projucer on Linux, try installing it via apt-get, e.g.:

sudo apt-get install libcurl4-openssl-dev

Missing headers

ft2build.h: No such file or directory:

Add /usr/include/freetype2 to Exporters | Linux Makefile | Debug and Release | Header Search Paths.

fatal error: gtk/gtk.h: No such file or directory:

Add /usr/include/gtk-3.0/gtk to Header Search Paths. (Unsure if this matters.)

sudo apt-get install libwebkit2gtk-4.0-37 libwebkit2gtk-4.0-dev

Your mileage may vary, but installing those packages and adding that header search path was all I needed to successfully build my first JUCE project. I was pleasantly surprised.

Building VSTs

When you build a Projucer VST project, the vsts don’t go to the build directory. When I didn’t see it there, I assumed something went wrong. But the VST actually built without problems. I found it in the directory listed in the “VST binary location” setting in Projucer.

File writer memory management

This is more of a “getting back into C++” issue than a JUCE issue, but I’ll tell you about it in case you also are getting back into C++ or trying C++ for the first time.

I wrote code to write an audio buffer out to a wav file that looked something like this:

juce::FileOutputStream outStream(outFile, outLen * channelCount);
juce::WavAudioFormat wavFormat;
juce::AudioFormatWriter *writer = wavFormat.createWriterFor(
    &outStream, sampleRate, channelCount, bitsPerSample, {}, 0
);
outStream.release();
writer->writeFromAudioSampleBuffer(outBuffer, 0, outLen);
delete writer;

When the delete was run, I’d get this error:

munmap_chunk(): invalid pointer
zsh: abort (core dumped)  ./build/vocode ../../example-media/donut.wav ../../example-media/talking.wav

But why? The docs for createWriterFor say:

The writer object that is returned can be used to write to the stream, and should then be deleted by the caller.

That’s me! I’m the caller! Or my function is, at least. It is supposed to clean up the writer object. So, why can’t it?

It turns out that the writer’s destructor tries to delete the output stream that you pass to createWriterFor. I found this out via the source, but if I had just read the docs for createWriterFor completely, I would have noticed that it said:

the stream that the data will go to - this will be deleted by the AudioFormatWriter object when it’s no longer needed.

So, when my function deletes the writer, the writer tries to delete the stream. However, because I allocated the stream object in the function scope in automatic storage instead of via new in dynamic storage, it can’t be deleted.

The fix was to put the stream in dynamic storage. (I almost said “on the heap” because I was taught it college that that’s where new-created objects went, but apparently, that’s no longer necessarily true among all implementations.)

I have not worked with C++ on the regular in quite a while, and I can’t emphasize enough to myself: Always know who’s responsible for every piece of memory you touch. I came into this thinking, oh, yeah, I gotta pay attention to memory, then got distracted by other things.

#linux #cpp #audio