Propeller 2 version of Auduino granular synthesizer
mpark
Posts: 1,305
I am slowly learning to program the Propeller 2 chip and as an exercise I decided to implement the remarkable Auduino granular synth on the P2. Auduino was originally written for the Arduino platform and is capable of producing a far wider range of timbres than might be expected given its relative simple design.
For my P2 implementation, I built a control panel with two analog joysticks and some potentiometers, and I added a MIDI interface. In the video below, a demonstration of how Auduino-style granular synthesis works is followed by some analog noodling, then some MIDI keyboard noodling, and finally a rendition of one of my favorite synth solos.
Here's the code. I would be grateful for any suggestions for improvement.
CON _xinfreq = 20_000_000 _clkfreq = 288_000_000 BASEPIN_DAC = 6 NPOTS = 7 iDECAY0 = 0 iINC0 = 1 iDECAY1 = 2 iINC1 = 3 iINC = 4 iVOL0 = 5 iVOL1 = 6 dat potPins byte 8, 9, 10, 11, 13, 12, 14 minValue word 0, 100, 0, 100, 0, 100, 0 maxValue long 200, 4000, 200, 4000, 2000, 1024, 1024 potValues long 0, 0, 0, 0, 0, 0, 0 var long stack1[40] long stack2[40] OBJ adc[NPOTS]: "jm_analog_in" midi: "jm_fullduplexserial" tx: "jm_fullduplexserial" var word midiNote word midiVel word inc pri midiTask() | state, b, s, n, l, bend bend := 0 << 24 ' +/- semitones << 24 midi.start(25, -1, 0, 31_250) midiNote := -1 ' something invalid repeat b := midi.rx() if b & $80 s := b & $f0 ' ignore channel state := 0 else if state == 0 ' note on/off note byte or pitch bend low byte if s == $90 or s == $80 n := b state := 1 elseif s == $e0 l := b state := 2 elseif state == 1 ' note on/off velocity if s == $90 ' note on midiNote := n midiVel := b inc := incFromNote(midiNote, bend) else 'note off if midiNote == n midiVel := 0 inc := 0 state := 0 elseif state == 2 ' pitch bend high byte bend := b << 7 + l ' 16-bit bend [0..16384) bend -= $2000 ' [-8192..8192) ' bend >> 12 would be [-2...2), +/- 2 semitones ' but we need to scale by 2^24, so: bend <<= 24 - 12 if midiVel inc := incFromNote(midiNote, bend) state := 0 pri incFromNote(note, bend) : result | freq, K { bend is semitones * 2^24 freq = 2 ^ ((note - 69) / 12) * 440 inc = freq / 48000 * 65536 } K := 16 freq := QEXP ((((note - 69) << 24 + bend) / 12) << 3 + K << 27) * 440 ' frequency << K result := freq / 48000 PUB main() playSounds(false) pri playSounds(isMidi) | i, ct, y , amp0, decay0, inc0, ph0, amp1, decay1, inc1, ph1, ph, out, vol0, vol1 cogspin(NEWCOG, potTask(), @stack1) cogspin(NEWCOG, midiTask(), @stack2) pinstart(BASEPIN_DAC addpins 1, P_DAC_124R_3V | P_DAC_DITHER_RND | P_OE, 6_000, 0) ' 48kHz wypin(BASEPIN_DAC addpins 1, $8000) waitms(500) ct := getct() repeat inc0 := potValues[iINC0] decay0 := potValues[iDECAY0] inc1 := potValues[iINC1] decay1 := potValues[iDECAY1] vol0 := potValues[iVOL0] vol1 := potValues[iVOL1] ifnot isMidi inc := potValues[iINC] ph += inc if ph.[16] ph.[16] := 0 ph0 := 0 amp0 := vol0 << 16 ph1 := 0 amp1 := vol1 << 16 y := ph0.[14..0] ' 0..7fff if ph0.[15] != y y -= $4000 ' -$4000..3fff y scas= amp0 out := y y := ph1.[14..0] ' 0..7fff if ph1.[15] != y y -= $4000 ' -$4000..3fff y scas= amp1 out += y out ^= $8000 waitct(ct += 6000) wypin(BASEPIN_DAC addpins 1, out) ph0 += inc0 ph1 += inc1 amp0 sca= $ffff_ffff + (decay0 - 200) << 16 amp1 sca= $ffff_ffff + (decay1 - 200) << 16 pri potTask() | i repeat i from 0 to NPOTS - 1 adc[i].start(potPins[i], minValue[i], maxValue[i]) repeat repeat i from 0 to NPOTS - 1 potValues[i] := adc[i].read() con { license } {{ Terms of Use: MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}
Comments
Neato!
I would suggest moving midiTask() and incFromNote(note, bend) below main() so there is no confusion where the program starts. I've been experimenting with MIDI a bit, but haven't fully sussed out dealing with my keyboard/controller (a 25-yr olf M-Audio Oxygen 8) that sends timing data mixed in with notes, and does running status style outputs).
Consider declaring a constant for your MIDI input pin, as that could move with different designs.
Given the rate of MIDI you could save a cog removing one instance of jm_fullduplexserial and using the smart pin directly.
Thank you for the great suggestions, @JonnyMac, and thanks for jm_analog_in and jm_fullduplexserial! Some day I'll understand smart pins.
I don't know how much time you have left in your 48kHz loop, but you might find a few space code cycles here and there by running simple timing tests like this:
Note that the second version is faster
A few minutes later...
Well, it turns out you have a lot of time in your loop, even if using Propeller Tool (not compiled).
I added pinlow() immediately before your waitct() and pinhigh() immediately after. You've got ~10us of available bandwidth.
nice
Thanks, @Ludis !