I just learned about the PCM interface, so I thought I'd do a little adventure into sound synthesis.
This cart is an experiment in building up an audio system that can run dataflow graphs built up from smaller generator and effect components. Here, I've used it to generate some plucked string sounds, mix those together, and then apply some delay to the result. The audio system needs some work and probably should implement audio processors as objects instead of coroutines (as coroutines it's annoying to send control signals once a processor has been created), but it's still kinda fun.
The code has a bunch of other areas that could use cleanup too, but coroutines vs. objects is probably the one big design issue that should get sorted out.
The one big audio-system-side thing that really helped here was trying to generate at least 128 samples per frame so long as I wasn't too far ahead of the audio system's needs. This really helped smooth out maximum CPU usage, since without that tweak I was sometimes generating 256 samples and sometimes 0.
On the plucked string side, with such a low sample rate it was absolutely necessary to include the tuning allpass filter in the delay loop. I've also included decay stretching as well as decay shortening ... yes, they're sort of at odds, but mixing them gives you a range of different sounds. I haven't done any pick direction/position filtering, string stiffness, bridge effects, sympathetic vibrations, dynamics filtering so louder notes are brighter, etc. But those could all be done here if anyone were so inclined!
The cart works well on my machine, but I'm very curious about whether it will sound okay on the web. I've tried to keep the volume down, but please be careful about your levels when you hit play.
As for the "composition", I started with a standard guitar tuning, but each string loops through a sequence of interval offsets from that tuning, advancing one step each time it gets hit. Not my favorite but it sort of works, I think.
Update: I added some ways to tweak the sound (stretch and damp) as well as the speed of the balls (dt). Up and down arrows select parameters, left and right change them. It's definitely more interesting this way.... For stretch and damp - low stretch values kill high frequencies faster, whereas low damp values tend to reduce all frequencies equally.
@luchak, I'm wondering if it would be easier to create a single note with this effect, say in SFX(0)
and then poke directly to it playing it changing only pitch instead of creating a new uniquely complex calculated sound each time ?
This is very cool! Good plucking sound, and I like the generative system you set up to demo it!
@dw817 Good question! I'm not so sure that will do what I want, though, for two reasons. First, these plucks aren't just transposed versions of each other. Frequencies decay at different rates depending on the string length. The character of the sound can also change quite a lot depending on how you set the two decay parameters, and I probably should be automating or randomizing those a bit more than I do here. Second, I don't think it's possible to store PCM data in a SFX channel? It would be possible to prerender a bunch of plucks and store them in Lua memory, but I'd still have to transpose them myself, plus runtime control would be more limited.
If I'm wrong and it is somehow possible to store PCM data in a SFX channel and play it back transposed, I'd love to know.... I know there are a few older threads on trying to approximate samples with carefully chosen SFX data, but I'm not sure that technique would get all that close here.
@packbat Thanks, I'm glad you liked it! I'm honestly kinda surprised this was possible in Pico-8. The plucked string basically follows Jaffe and Smith's extensions of the Karplus-Strong model.
This is PCM then, @luchak ? Oh, well that's quite new territory. Hmm ... And yes that was a question I had. Is it possible to play a PCM at different pitches in Pico-8 ?
I haven't delved into PCM yet, I'm pretty satisfied with the ability to use one instrument as the source for another with the green flag. Yet we still cannot have starship rumble and the lowest pitch for SFX can never reach this brown noise level.
I wish it could. I imagine a lot of people would like the pitch for any instrument to reach to the "too low for a human to hear" and "too high for a human to hear."
The Commodore 64 soundcard had that ability.
In any case, gold star for your superbly tuned and melodic guitar.
Oh, and thanks again for the introduction to YIELD(). That is a real life-saver for the RG-350 !
> Is it possible to play a PCM at different pitches in Pico-8 ?
You can do whatever you want, so long as you do it at 5512 Hz! You query the runtime to ask how many samples are left in the audio buffer, and if it says it's close to being out, you generate more samples. With such a low sample rate and no antialiasing filter on the output, the results are always going to be kind of lo-fi, but it's still way more flexible than the built-in sound engine. (Though far, far less convenient, of course.)
I'm also glad that coroutines solved your flip problem! :) Generating audio is a little bit like animation in that you have a lot of long-running tasks that need to preserve state across invocations, so your problem helped prime me for working on this....
This next experiment is working much better than I thought it would! At least on my machine. The web player has some audio issues.
I migrated the audio driver code from the demo at the top of the thread to use objects. I have a simple saw oscillator (no antialiasing at all) running through the ladder filter from the end of this thread and with 4x oversampling right now it doesn't sound half bad and uses about 20% of the CPU. The oversampling is janky and untuned just using one-pole upsampling/downsampling filters but oh well.
The filter is just on the very edge of instability right now, and it does sound dramatically better at 8x or 16x oversampling (at least with these settings) ... but I'd love to have enough CPU to have two of these synths running, with some extra left over for some drum synthesis, FX, and sequencing/UI, if you see what I'm thinking here.
I probably have some awful bugs and general poor tuning, I know I'm not calculating the filter constants quite right from frequency, etc. but this is seeming pretty promising.
As for the audio issues, I'm wondering if I can maybe mitigate those on the web if I use my own main loop and allow audio to drive update timing instead of video?
Huh. I'm stumped. I can consistently have 1000+ samples of buffer on the web player and it will still crackle. I'm assuming that's because it's waiting for my code to yield back so it can do whatever audio stuff it needs to do, and if I don't yield back often enough, it crackles. But this happens even if stat(1)
never shows more than 0.15, so that seems strange too. I wondered if I might have some kind of clipping problem, but reducing volume doesn't need to help either.
Am I misunderstanding the cause of my issue? If not, Is there a way to yield time back to the runtime other than flip() - specifically, that yields back, but without waiting for the next frame like flip() does?
edit: ah, okay. Both carts play back fine on Chrome on Windows, and both play back with crackles on Chrome on my Mac. I just switched which machine I was using between those two carts. Of course I would still love to fix this....
I've started on some UI. Nothing's interactive yet, but I thought it might be interesting to look at anyway. There are a couple of minor sound changes too: I added a bit of soft clipping, and note timing now depends on audio status rather than on when _update60() is called.
I'm hoping to make this cart usable with just the standard PICO-8 controls. The current plan is to use the arrow keys to navigate: the focused control will have a little box around it, and pressing an arrow key will switch focus to the next control in that direction. Then z/x will either toggle (for switches) or change the value up/down (notes or continuous controls).
That scheme still seems a little awkward, but it's the best thing I can think of right now. Once the cart is interactive I'll start a new WIP thread for it.
Web audio is, as usual, kinda awful on some browsers/OSes and fine on others. Cart runs fine locally everywhere I've tried it.
You've got the makings of some awesome music here, @luchak. Real cutting edge that previous to your writing did not exist. Superb work.
I'm - curious to see how far you can go with this. Something I wanted to do but I'm not really understanding PCM, is to take a single real WAV piano note and play it in Pico-8, changing pitch, to play music.
Thanks! I think I might be shooting for a demake of Rebirth. Hopefully I can get in at least the two synths, plus one drum machine, and at least distortion and delay (both already present) for effects. We'll see how far I get! If I make reasonable progress I think I might end up generating a nice little UI and DSP toolbox for building other groovebox-ish things in PICO-8.
As for the piano question - it looks like someone did it, at least enough to have a proof of concept cart. It seems like you also saw it, though, so maybe I'm misunderstanding what you have in mind. The hard part here is less playback and more cramming a reasonable quantity of sample data into a single cart, since even with PICO-8's 8 bits of dynamic range and ~5.5kHz sampling rate you won't get more than a few seconds without some pretty serious hacks.
[Please log in to post a comment]