[sdiy] programmable synth module
Grant Richter
grichter at asapnet.net
Thu Feb 16 09:12:13 CET 2006
Basic Micro was supposed to bring out the 40 pin version, which why
the 40 pin footprint is on the PCB.
They did at least being out the 28 pin version which has 4 more pins.
Using the I2C lines, existing DAC lines and the 4 extra pins you
could get byte wide data transfers
to up to 256 outputs.
I'm attaching Mike Murphy's public domain 3D sequencer program that
does envelopes and pitch control.
'PUBLIC DOMAIN ... OPEN SOURCE FOREVER
'=========== 3DSEQ =========
'=========== 25 Feb 2004=========
' (happy B'day Maureen!)
'by Dr. Mabuse for the Modern Implement Company
'
'---------REVISED 15 April 2004--------
'to accomodate the peccadilloes of compiler version 7.2.0.1
'--------------------------------------
' LOADALLDACS & SCANADC
' by Prof. Grant Richter
' all hail his sublime woggleness
'
'description:
'
' a sequencer that outputs:
'
' a complex CV from CVout1
' a variable Attack/Release envelope from CVout2
' a trigger at the start of each event from CVout3
' a vibrato-only output from CVout4 (that follows the vibrato on CVout1)
'
'Each step has:
'a pitch: PITCH
'a duration: DUR
'a slew flag; SLEW 1 = slew to the NEXT pitch in steps scaled to the
duration of THIS step; THIS TAKES PRECEDENCE OVER THE VIBRATO FLAG
'a Vibrato flag; VIBF 1 = apply vibrato (to pitch) at a rate scaled
to the duration and die out over that duration
'a Break point; BKPT this defines an envelope that is output from
CVout2. The breakpoint must be <= DUR, if DOR = 1000 the a BKPT of 1
would output a downgoing ramp, a BKPT of 1000 would output an upgoing
ramp, a BKPT of 500 would output a triangle
'
'inputs:
'Cvin1 determines the probablility that the sequenence steps will be
reordered 0=no chance, >8v = reordered after every cycle, then 1 to 7
out of 8 chances in-between
'CVin2 < 3v=do not randomize the envelopes. >3v = randomize the
envelopes after every cycle.
'Start Button (IN4) reset all envelope breakpoints to their original
values (established by the BKPT array)
'
'-------------------------------------------------
' Basic Micro Atom Pro-24M Configuration
'
' (Note: P0 is I/O 0 and NOT pin 0 on the microprocessor.)
'
' P0 - Analog IN-1 (0-5 VDC)
' P1 - Analog IN-2 (0-5 VDC)
' P2 - Analog IN-3 (0-5 VDC)
' P3 - Analog IN-4 (0-5 VDC)
' P4 - START Button (Momentary Normally Open Switch)
' P5 - STOP Button (Momentary Normally Open Switch)
' P6 - I2C/SDA (Reserved) - J3 Pin 1
' P7 - I2C/SDL (Reserved) - J3 Pin 2
' P8 - AUX (Digital I/O - NO BUFFERING)
' P9 - STOP LED
' P10 - RUN LED
' P11 - DAC - LOADDACS
' P12 - DAC - SERDATA
' P13 - DAC - CLOCK
' P14 - RXD (Reserved) - J5 Pin 1 (Midi)
' P15 - TXD (Reserved) - J5 Pin 2 (Midi)
'-------------------------------------------------------
'Define Variables
LOADDACS CON 11 ' Pin OUT to DAC LOADDACS
SERDATA CON 12 ' Pin OUT Serial Data to DAC (16-bit)
CLOCK CON 13 ' Pin OUT to Clock DAC
STOPLED CON 9 ' Red LED
RUNLED CON 10 ' Green LED
BSTART CON 5 ' Start Button
BSTOP CON 4 ' Stop Button
AUX CON 8 ' AUX Jack (unbuffered)
RAWDAC1 VAR WORD ' RAW DAC DATA 1
RAWDAC2 VAR WORD ' RAW DAC DATA 2
RAWDAC3 VAR WORD ' RAW DAC DATA 3
RAWDAC4 VAR WORD ' RAW DAC DATA 4
DAC1V VAR WORD ' DAC Value to be Sent to DAC Channel
DAC2V VAR WORD ' DAC Value to be Sent to DAC Channel
DAC3V VAR WORD ' DAC Value to be Sent to DAC Channel
DAC4V VAR WORD ' DAC Value to be Sent to DAC Channel
ADC1 CON 0
ADC2 CON 1
ADC3 CON 2
ADC4 CON 3
ADC1V VAR WORD 'INPUT A/D BUFFER CH. 1
ADC2V VAR WORD 'INPUT A/D BUFFER CH. 2
ADC3V VAR WORD 'INPUT A/D BUFFER CH. 3
ADC4V VAR WORD 'INPUT A/D BUFFER CH. 4
J VAR WORD
FJ VAR FLOAT
STARTAT VAR SBYTE
ENDAT VAR SBYTE
DEST VAR SWORD
FDEST VAR FLOAT
WORKVAR VAR FLOAT
STEPSIZE VAR FLOAT
AMPLITUDE VAR FLOAT
CPTS VAR FLOAT
ICPTS VAR WORD
UPDN VAR BIT
EVOTHR VAR BYTE
REPCT VAR WORD
REPS VAR WORD
FDUR VAR FLOAT
SUBX VAR FLOAT
ISUBX VAR WORD
OFFSET VAR SWORD
PITCHCTR VAR WORD
PCV VAR WORD
PCV1 VAR WORD
NOW VAR BYTE
NXT VAR BYTE
DIFF VAR SWORD
CHUNK VAR WORD
VIBCTR VAR WORD
ITMP VAR WORD
ISTART VAR WORD
IEND VAR WORD
IAMP VAR WORD
PITCH VAR WORD(16)
DUR VAR WORD(16)
BKPT VAR WORD(16)
ENVP VAR WORD(16)
SLEW VAR BIT(16)
VIBF VAR BIT(16)
VALW1 VAR WORD
VALB1 VAR BIT
BPM VAR WORD
RSEED VAR WORD
RCHANCE VAR WORD
PCHANCE VAR WORD
RDEX1 VAR WORD
RDEX2 VAR WORD
RFACT VAR WORD
RDEX VAR WORD
COIN VAR WORD
EOUT VAR SWORD
ECTR VAR SWORD
EOFF VAR SWORD
FEOFF VAR FLOAT
FENVP VAR FLOAT
ESTEPSZ VAR FLOAT
'*****************************************************
'Initialize Module
DIRS = %1111110000000000 ' Configure Pins 1=input 0=output
OUTS = %1111111111111111 ' Configure State 1=low 0=high
' *****************************************************
LOW STOPLED
LOW RUNLED
VIBCTR = 200; center frequency of vibrato-only output (dac4v)
RSEED = 17
'seed ARRAYs that define the sequence
PITCH( 1) = 40
DUR( 1) = 500
SLEW( 1) = 0
VIBF( 1) = 0
BKPT( 1) = 250
PITCH( 2) = 64
DUR( 2) = 500
SLEW( 2) = 1
VIBF( 2) = 0
BKPT( 2) = 1
PITCH( 3) = 128
DUR( 3) = 500
SLEW( 3) = 0
VIBF( 3) = 0
BKPT( 3) = 499
PITCH( 4) = 256
DUR( 4) = 100
SLEW( 4) = 0
VIBF( 4) = 0
BKPT( 4) = 50
PITCH( 5) = 1024
DUR( 5) = 500
SLEW( 5) = 0
VIBF( 5) = 0
BKPT( 5) = 1
PITCH( 6) = 40
DUR( 6) = 500
SLEW( 6) = 0
VIBF( 6) = 0
BKPT( 6) = 250
PITCH( 7) = 256
DUR( 7) = 2000
SLEW( 7) = 0
VIBF( 7) = 0
BKPT( 7) = 1
PITCH( 8) = 320
DUR( 8) = 500
SLEW( 8) = 0
VIBF( 8) = 1
BKPT( 8) = 1
PITCH( 9) = 1588
DUR( 9) = 100
SLEW( 9) = 0
VIBF( 9) = 0
BKPT( 9) = 1
PITCH(10) = 1492
DUR(10) = 500
SLEW(10) = 1
VIBF(10) = 0
BKPT(10) = 1
PITCH(11) = 64
DUR(11) = 500
SLEW(11) = 0
VIBF(11) = 0
BKPT(11) = 1
PITCH(12) = 256
DUR(12) = 1140
SLEW(12) = 0
VIBF(12) = 1
BKPT(12) = 1140
PITCH(13) = 3000
DUR(13) = 500
SLEW(13) = 0
VIBF(13) = 0
BKPT(13) = 1
PITCH(14) = 332
DUR(14) = 500
SLEW(14) = 1
VIBF(14) = 0
BKPT(14) = 1
PITCH(15) = 870
DUR(15) = 100
SLEW(15) = 0
VIBF(15) = 0
BKPT(15) = 1
PITCH(16) = 1087
DUR(16) = 500
SLEW(16) = 0
VIBF(16) = 1
BKPT(16) = 1
GOSUB RESTORENV
NOW=0
MAINLOOP:
GOSUB SCANADC
DAC4V = VIBCTR; output vibrato-only center frequency
DAC3V = 0
GOSUB LOADALLDACS; start TRIGGER ouput on CVout3
DAC3V = 4095
GOSUB LOADALLDACS; leading/rising edge of TRIGGER ouput on CVout3
'
' this is the 'homemade' 1 thru 16 loop counter- it is circular, ie
when NOW = 16, NXT = 1...forever
NOW = NOW + 1
IF NOW >= (16+1) THEN
NOW = 1
ENDIF
IF NOW = 16 THEN
NXT = 1
ELSE
NXT = NOW + 1
ENDIF
'
'probablility knob - as CV in goes up chances increase that event
will happen
'
PCHANCE = (ADC1V/128)+1;quantize Cv in 1 to 1 thru 8
IF PCHANCE <= 1 THEN
HIGH RUNLED
GOTO NOPCHG; if CV is OFF then don't swap events
ENDIF
IF PCHANCE >= 8 THEN
GOSUB SWAPEVENTS
GOTO NOPCHG
ENDIF ; if CV if full on then ALWAYS swap events
PCHANCE = 9- PCHANCE; invert pchance so that 8 increases the
chances that something will happen
RSEED = RANDOM RSEED; cast the dice
RSEED = RSEED + 1; preclude division by zero
RCHANCE = RSEED/(65535/PCHANCE);scale random number to current
range of CVin1
RCHANCE = RCHANCE+1
IF PCHANCE = RCHANCE THEN
GOSUB SWAPEVENTS
ENDIF
NOPCHG:;exit point
IF ADC2V > 300 THEN;if CUin2 is high then randomize all the envelope
breakpoints
GOSUB RANDENV
ENDIF
IF IN4 THEN;restore all envelope breakpoints to their orginal values
- in the BKPT array
GOSUB RESTORENV
ENDIF
DAC3V = 0
GOSUB LOADALLDACS; trailing edge of TRIGGER ouput on CVout3
IF (PITCH(NXT) <> PITCH(NOW)) AND (SLEW(NOW) > 0) THEN; a SLEW event
PCV1 = PITCH(NOW)
ECTR = 0; initialize loop counter for envelopre generator ENVGEN
DAC2V = 0
GOSUB LOADALLDACS; force envelope ouput on CVout2 to start @ 0
DIFF = (PITCH(NXT) - PITCH(NOW) )
DIFF = ABS(DIFF)
CHUNK = (DIFF/DUR(NOW))
IF CHUNK < 1 THEN
CHUNK = 1
ENDIF
IF PITCH(NXT) > PITCH(NOW) THEN
GOINGUP:
ECTR = ECTR + CHUNK; increment loop counter for envelope
generator ENVGEN
PCV1 = PCV1 + CHUNK
IF PCV1 >= PITCH(NXT) THEN
DAC2V = 0
GOSUB LOADALLDACS; force envelope ouput on CVout2 to end @ 0
GOTO ENDMAINLOOP
ENDIF
DAC1V = PCV1
GOSUB ENVGEN; also outputs DAC1V to LOADALLDACS
GOTO GOINGUP
ELSE
GOINGDOWN:
ECTR = ECTR + CHUNK; increment loop counter for envelope
generator ENVGEN
PCV1 = PCV1 - CHUNK
IF PCV1 <= PITCH(NXT) THEN
DAC2V = 0
GOSUB LOADALLDACS; force envelope ouput on CVout2 to end @ 0
GOTO ENDMAINLOOP
ENDIF
DAC1V = PCV1
GOSUB ENVGEN; also outputs DAC1V to LOADALLDACS
GOTO GOINGDOWN
ENDIF
ELSE; NOT a slew event ....either plain or vibrato
PCV1 = PITCH(NOW)
IF VIBF(NOW) THEN
GOSUB VIBRATO; it's......VIBRATO TIME!!!!!
ELSE; PLAIN VANILLA EVENT NO SLEW , NO VIBRATO
DAC2V = 0
GOSUB LOADALLDACS; force envelope ouput on CVout2 to start @ 0
J=0
FOR J = 1 TO DUR(NOW)
DAC1V = PCV1
ECTR = J
GOSUB ENVGEN; also outputs DAC1V to LOADALLDACS
NEXT
DAC2V = 0
GOSUB LOADALLDACS; force envelope ouput on CVout2 to end @
0
ENDIF
ENDIF
ENDMAINLOOP:
GOTO MAINLOOP
'*******************************************************************
'************************** SUBROUTINES ****************************
'*******************************************************************
'all hail GR!
LOADALLDACS:
'Add addresses to values no speed improve with OR over +
RAWDAC1=DAC1V+49152
RAWDAC2=DAC2V+32768
RAWDAC3=DAC3V+16384
RAWDAC4=DAC4V
'shift out 16 bits mode 4 gotta bang loaddacs pin for each channel
'skew from ch. 1 to 4 = 400 usecs. Aprox 1 msec execution time for sub.
SHIFTOUT SERDATA,CLOCK,4,[RAWDAC1\16]
PULSOUT LOADDACS,1
SHIFTOUT SERDATA,CLOCK,4,[RAWDAC2\16]
PULSOUT LOADDACS,1
SHIFTOUT SERDATA,CLOCK,4,[RAWDAC3\16]
PULSOUT LOADDACS,1
SHIFTOUT SERDATA,CLOCK,4,[RAWDAC4\16]
PULSOUT LOADDACS,1
RETURN
'all hail GR!
SCANADC:
'load buffers with actual a/d values
ADIN ADC1, ADC1V
ADIN ADC2, ADC2V
ADIN ADC3, ADC3V
ADIN ADC4, ADC4V
RETURN
VIBRATO:
'setup overall variables for the event:
REPS = DUR(NOW)/32;set a constant vibrato freq at 1/32 of the overall
event duration
'the 32 is also a factor in the overal amplitude of the vibrato the
max will be 32/2 = 16 = an approximate quarter-tone
REPCT = REPS; initialize the state of the repetition counter
ICPTS = REPS/2; the change point - CPTS is the half point of the
triangle wave - the interval after which each change occurs
UPDN = 1; 1 = AWAY from center freq. , 0 = TOWARD center frequency
STARTAT=0
ENDAT=0
EVOTHR=0; counter to indicate EVERY OTHER change point
DEST=ICPTS; DEST = Destination = it's time to change direction or
cross zero
DAC2V = 0
GOSUB LOADALLDACS; force envelope ouput on CVout2 to start @ 0
'
'loop thru the vibrato event.......
'
'
'
'this first section builds the vibrato by establishing the endpoints
(STARTAT & ENDAT) of lines that zig-zag across the center frequency
'
FOR J = 1 TO DUR(NOW)
FJ = TOFLOAT(J)
IF J = DEST THEN;it's time to change direction or cross zero
ISUBX=J; store subtractor for later use in scaling the
'backwards counter'
DEST = DEST + ICPTS; establish the NEXT change point
EVOTHR = EVOTHR + 1
IF EVOTHR = 2 THEN;EVERY OTHER change point: toggle the
polarity of the wave
EVOTHR=0; reset the every other counter
STARTAT=ENDAT
ENDAT=0
IF UPDN = 1 THEN
UPDN = 0
ELSE
UPDN = 1
ENDIF
ELSE; at the peak (midpoint) of the wave, simply reverse
its direction
IF 32 - (REPS-REPCT) >= 0 THEN
STARTAT = ENDAT
ENDAT = 32 - (REPS-REPCT)
IF UPDN = 0 THEN
ENDAT = -ENDAT
ENDIF
REPCT = REPCT - 1
ELSE
STARTAT = ENDAT
ENDAT = 0
ENDIF
ENDIF
ENDIF
'
'this second section calulates the interpolation function (rise/run)
between STARTAT & ENDAT for each integer value of J
'
' setup variables for interpolation and convert integers to
floating point where necessary
CPTS = TOFLOAT(ICPTS)
FDEST = TOFLOAT(DEST)
SUBX = TOFLOAT(ISUBX)
IAMP = ABS( ABS(STARTAT) - ABS(ENDAT) )
AMPLITUDE = TOFLOAT(IAMP)
STEPSIZE = 0.00
AMPLITUDE = AMPLITUDE / 2.00
CPTS = TOFLOAT(ICPTS)
'now do a different interpolation for every type of line segment
defined by STARTAT & ENDAT
IF STARTAT = 0 AND ENDAT > 0 THEN;slope from 0 to +peak
ITMP = TOINT(WORKVAR); required for first time thru this
section only
WORKVAR = TOFLOAT(ITMP)
WORKVAR= ((FDEST-(FDEST-FJ))-SUBX); condition J to rise from 0
STEPSIZE = AMPLITUDE / CPTS; calculate a term proportional to
the distance already traveled toward ENDAT
WORKVAR = WORKVAR * STEPSIZE; combine rise/run-point for each J
OFFSET=TOINT(WORKVAR);round, and reconcile variable type for
export to DAC
ENDIF
IF STARTAT > 0 AND ENDAT = 0 THEN;slope from +peak to 0
ITMP = TOINT(WORKVAR); required for first time thru this
section only
WORKVAR = TOFLOAT(ITMP)
WORKVAR= (FDEST-FJ); condition J to fall toward 0
STEPSIZE = AMPLITUDE / CPTS; calculate a term proportional to
the distance already traveled toward ENDAT
WORKVAR = WORKVAR * STEPSIZE; combine rise/run-point for each J
OFFSET=TOINT(WORKVAR);round, and reconcile variable type for
export to DAC
ENDIF
IF STARTAT = 0 AND ENDAT < 0 THEN;slope from 0 to -peak
ITMP = TOINT(WORKVAR); required for first time thru this
section only
WORKVAR = TOFLOAT(ITMP)
WORKVAR= ((FDEST-(FDEST-FJ))-SUBX); condition J to fall from 0
STEPSIZE = AMPLITUDE / CPTS; calculate a term proportional to
the distance already traveled toward ENDAT
WORKVAR = WORKVAR * STEPSIZE; combine rise/run-point for each J
OFFSET = TOINT(WORKVAR); load variable to be summed with
center frequency
ENDIF
IF STARTAT < 0 AND ENDAT = 0 THEN;slope from -peak to 0
ITMP = TOINT(WORKVAR); required for first time thru this
section only
WORKVAR = TOFLOAT(ITMP)
WORKVAR= (FDEST-FJ); condition J to rise toward 0
STEPSIZE = AMPLITUDE / CPTS; calculate a term proportional to
the distance already traveled toward ENDAT
WORKVAR = WORKVAR * STEPSIZE; combine rise/run-point for each J
OFFSET = TOINT(WORKVAR); load variable to be summed with
center frequency
ENDIF
ICPTS=TOINT(CPTS)
'Add OFFSET to center frequency and load it into the DAC
PCV = PCV1 + OFFSET
DAC1V = PCV
DAC4V =VIBCTR + (OFFSET * 4); setup the vibrato only output
ECTR = J
GOSUB ENVGEN; also outputs DAC1V to LOADALLDACS
NEXT
DAC2V = 0
GOSUB LOADALLDACS; force envelope ouput on CVout2 to end @ 0
RETURN
SWAPEVENTS:
HIGH STOPLED
RSEED = RANDOM RSEED; flip a coin to determine if vibrato flag gets
toggled
COIN = RSEED/32768
IF COIN > 0 THEN; Toggle vibrato flag
IF VIBF(NOW) = 1 THEN
VIBF(NOW) = 0
ELSE
VIBF(NOW) = 1
ENDIF
ENDIF
RFACT = 65535/16;'sets scale of random number to size of array
'two array elements exchange places
RSEED = RANDOM RSEED; cast dice
RDEX1 = RSEED/RFACT; YIELDS RANGE OF 0 THRU 16
ROLLEDDOUBLES:
RSEED = RANDOM RSEED; cast dice
RDEX2 = RSEED/RFACT; YIELDS RANGE OF 0 THRU 16
IF RDEX2 = RDEX1 THEN ROLLEDDOUBLES; no good to exchance same
array element with same - do over!
VALW1 = PITCH(RDEX1);>>>>>>>>>>>
PITCH(RDEX1) = PITCH(RDEX2);>>>>>
PITCH(RDEX2) = VALW1;>>>>>>>>>>> exchange the PITCH @ RDEX1 with
the one @ RDEX2
VALW1 = DUR(RDEX1);>>>>>>>>>>>
DUR(RDEX1) = DUR(RDEX2);>>>>>
DUR(RDEX2) = VALW1;>>>>>>>>>>> exchange the PITCH @ RDEX1 with
the one @ RDEX2
VALW1 = ENVP(RDEX1);>>>>>>>>>>>
ENVP(RDEX1) = ENVP(RDEX2);>>>>>
ENVP(RDEX2) = VALW1;>>>>>>>>>>> exchange the ENVELOPE BREAKPOINT
@ RDEX1 with the one @ RDEX2
VALB1 = SLEW(RDEX1);>>>>>>>>>>>
SLEW(RDEX1) = SLEW(RDEX2);>>>>>
SLEW(RDEX2) = VALB1;>>>>>>>>>>> exchange the SLEWFLAG @ RDEX1
with the one @ RDEX2
VALB1 = VIBF(RDEX1);>>>>>>>>>>>
VIBF(RDEX1) = VIBF(RDEX2);>>>>>
VIBF(RDEX2) = VALB1;>>>>>>>>>>> exchange the VIBRATO FLAG @
RDEX1 with the one @ RDEX2
LOW STOPLED
RETURN
ENVGEN:
IF ECTR <= ENVP(NOW) THEN
EOFF = ENVP(NOW) - (ENVP(NOW) - ECTR )
FEOFF = TOFLOAT(EOFF)
FENVP = TOFLOAT(ENVP(NOW))
ESTEPSZ = 4095.00 / FENVP
FEOFF = FEOFF * ESTEPSZ
EOUT = TOINT(FEOFF)
DAC2V = EOUT
GOSUB LOADALLDACS
ELSE
EOFF = ENVP(NOW) - ECTR
FEOFF = TOFLOAT(EOFF)
FENVP = TOFLOAT(ENVP(NOW))
FDUR = TOFLOAT(DUR(NOW))
ESTEPSZ = FDUR - FENVP
ESTEPSZ = 4095.00 / ESTEPSZ
FEOFF = FEOFF * ESTEPSZ
EOUT = TOINT(FEOFF)
EOUT = 4095 + EOUT
IF EOUT < 0 THEN
EOUT = 0
ENDIF
DAC2V = EOUT
GOSUB LOADALLDACS
ENDIF
RETURN
RESTORENV:
FOR J = 1 TO 16
ENVP(J) = BKPT(J)
NEXT
RETURN
RANDENV:
FOR J = 1 TO 16
RFACT = 65535/DUR(J);'sets scale of random number to duration of
event
RSEED = RANDOM RSEED; cast dice
ENVP(J) = RSEED/RFACT; yields range with duration
IF ENVP(J) = 0 THEN
ENVP(J) = 1
ENDIF
NEXT
RETURN
More information about the Synth-diy
mailing list