'PUBLIC DOMAIN ... OPEN SOURCE FOREVER
'===========  1DSEQMIDI =========
'=========== 22 Apr 2004=========
'by Dr. Mabuse for the Modern Implement Company
'MIDI interface by Dave Brown
'
'---------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 stepped CV from CVout1
' a variable Attack/Release envelope from CVout2
' a trigger at the start of each event from CVout3
' 
'Each step has:
'a pitch: PITCH
'a duration: DUR
'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)
'
'MIDI beat clock (0xF8) is required to run the sequence
'
'Durations (DUR) are setup in increments of 32nd notes thus:
'1 = a step of One 32nd note
'2 = 16th note
'3 = dotted 16th note
'4 = 8th note
'8 = quarter note
'etc...
'
'Envelope Breakpoints (BKPT) must be an integer between 1 and that steps Duration (DUR)
'envelopes are output in 'zippered' steps due to the 24ppq quantization enforced by MIDI beat clock 
' 
'-------------------------------------------------
' 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)
'-------------------------------------------------------
'*****************************************************
'
'  ###   CONDITIONAL COMPILING OPTION    ###
'  Set POLLED or INTERRUPT driven MIDI input
'
'Comment out next line for polled mode
	IM_FLAG	CON 1	'Defined for interrupt mode
					'Commented for polled mode
'
'*****************************************************
'
	'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)
	
	
	MIDICHAN	VAR	BYTE
	MIDINOTE	VAR BYTE
	MIDIVEL		VAR	BYTE
'	
	MIDIDATA	VAR	BYTE				'MIDI data variable for subroutines
	I_DATA		VAR	BYTE				'MIDI data variable for interrupt routine
	STATUS		VAR BYTE				'MIDI status data
	RPTR_S		VAR BYTE				'Received data start pointer (first data in buffer)
	RPTR_E		VAR BYTE				'Received data end pointer (last data in buffer+1)
	RBUFNUM		VAR	BYTE				'Number of bytes in received data buffer
	RDATAF		VAR BYTE				'Received data flag 1=data, 0=no data
	RBUFL		CON	32					'Receive buffer length
	RBUF		VAR BYTE(RBUFL)			'Receive buffer data
'
    PPQ         VAR BYTE 'counter for 24 pulses per quarter note
    
	TIMECOUNT	VAR LONG				'1 mS timer count
	TURNOFF		VAR LONG				'Time to turn off stop led
'

	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
FDUR      VAR FLOAT
PCV       VAR WORD
PCV1      VAR WORD
NOW       VAR BYTE
NXT       VAR BYTE

PITCH     VAR WORD(16)
DUR       VAR WORD(16)
BKPT      VAR WORD(16)
ENVP      VAR WORD(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 PSIM
	DIRS = %0111110000000000			'Configure Pins: 1=output, 0=input
	OUTS = %0111111111111111			'Configure state: 1=high, 0=low
'
'Initialize serial hardware and ports
'These next 6 commands need to be in this order!
	LET SCR3=%00000000					'Reset Serial Control Register
	LET SMR=%00000000					'Set Serial Mode Register
	 									' Asynchronous
	 									' 8 bits
	 									' Parity disabled
		 								' Even parity (disabled)
	 									' 1 stop bit
	 									' Multiprocessor mode disabled
	 									' BRR clock source direct
	LET BRR=15							'Set Bit Rate Register for 31500 baud
	PAUSEUS 100							'Let BRR settle for 50 uS
	LET SCR3=%00110000					'Set Serial Control Register
	 									' Transmit or receive interrupts disabled
	 									' Transmit and receive enabled
	 									' Multiprocessor interrupt disabled
	 									' Transmit end interrupt disabled
	 									' Internal baud rate generator
	LET PMR1=%00001110					'Set Port Mode Register
	 									' P17 general I/O port
	 									' P16 general I/O port
	 									' P15 general I/O port
	 									' P14 general I/O port
	 									' TXD output
	 									' P10 general I/O port
'
'setup timerw for 1 millisecond interupts
	 LET TMRW=%10001000 				'Set Timer Mode Register to enable count
	 LET TCRW=%10110000 				'Set Timer Control Register
	 									' TCNT cleared by compare match
	 									' /8 internal clock
	 LET TIERW=%01110000				'Set Timer Interrupt register to disable overflow interrupt
	 LET TSRW=%01110000					'Set Timer Status Register to default
	 LET TIOR0=%10001000				'Set Timer I/O Regiseter 0 to default
	 LET TIOR1=%10001000				'Set Timer I/O Register 1 to default
	 LET GRA=2000 						'Set General Register A
	 									' 16 MHz clock /8 = 2 MHz
	 									' 2000 counts = 1 millisecond interrupt
'
'Initialize variables
	LET RPTR_S=0						'Initialize receive start pointer
	LET RPTR_E=0						'Initialize receive end pointer
	LET RBUFNUM=0						'Set receive buffer to empty
	LET MIDICHAN=0						'Set MIDI channel to 0
	LET MIDIVEL=$40						'Set default velocity
	LET TIMECOUNT=0						'Set real time counter to 0
	LET TURNOFF=0						'Set time to turn off to TIMECOUNT
'
	ONINTERRUPT TIMERWINT_IMIEA, CALCTIME 
	#IFDEF IM_FLAG
		ONINTERRUPT SCI3INT_RDRF, MIDI_IN_ISR
		ENABLE SCI3INT_RDRF				'Enable receive interrupt
	#ENDIF
	ENABLE TIMERWINT_IMIEA				'Enable timer interrupt
'
'*****************************************************
LOW STOPLED
LOW RUNLED


RSEED = 17

'seed ARRAYs that define the sequence
PITCH( 1) = 240
  DUR( 1) = 4
 BKPT( 1) = 1
PITCH( 2) = 64
  DUR( 2) = 8
 BKPT( 2) = 1
PITCH( 3) = 128
  DUR( 3) = 4
 BKPT( 3) = 2 
PITCH( 4) = 256
  DUR( 4) = 16
 BKPT( 4) = 3
PITCH( 5) = 1024
  DUR( 5) = 2
 BKPT( 5) = 1
PITCH( 6) = 240
  DUR( 6) = 4
 BKPT( 6) = 4 
PITCH( 7) = 256
  DUR( 7) = 2
 BKPT( 7) = 1 
PITCH( 8) = 320
  DUR( 8) = 2
 BKPT( 8) = 1 
PITCH( 9) = 1588
  DUR( 9) = 2
 BKPT( 9) = 1 
PITCH(10) = 1492
  DUR(10) = 4
 BKPT(10) = 1  
PITCH(11) = 64
  DUR(11) = 8
 BKPT(11) = 1 
PITCH(12) = 256
  DUR(12) = 4
 BKPT(12) = 3 
PITCH(13) = 300
  DUR(13) = 16
 BKPT(13) = 8
PITCH(14) = 332
  DUR(14) = 2
 BKPT(14) = 1
PITCH(15) = 470
  DUR(15) = 4
 BKPT(15) = 1 
PITCH(16) = 1087
  DUR(16) = 4
 BKPT(16) = 1 
 
GOSUB RESTORENV 
 
NOW=0 
 
MAINLOOP:

GOSUB SCANADC


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
     

    PCV1 = PITCH(NOW)
    DAC2V = 0
    GOSUB LOADALLDACS; force envelope ouput on CVout2 to start @ 0		   
    J=0
    FOR J = 1 TO DUR(NOW)
    
          PPQ=0
          MAINMIDI:
          IF PPQ = 3 THEN
                 GOTO AVANTI
           ENDIF
	       #IFNDEF IM_FLAG
	        GOSUB CHECK_MIDI		'Poll MIDI input if not interrupt driven
	       #ENDIF
	       IF RBUFNUM<>0 THEN	     'Check to see if any MIDI data received
	         GOSUB GET_RBUFFER	     'Get data from received buffer
	         HIGH STOPLED            ' tell the people we got MIDI!
             IF MIDIDATA = 0xF8 THEN 'hex F8 is MIDI beat clock, SYSTEM REALTIME MESSAGE. 24 of these critters go by during every QUARTER NOTE 
                    PPQ = PPQ + 1    'increment PPQ every time hex F8 goes by
              ENDIF				
	         DISABLE TIMERWINT_IMIEA 
	         TURNOFF=TIMECOUNT+20	 'Set time to turn off as current + 20 mS
	         ENABLE TIMERWINT_IMIEA 
        	ENDIF
	       GOTO MAINMIDI
	      
          AVANTI:
           DAC1V = PCV1
           ECTR = J
           GOSUB ENVGEN; also outputs DAC1V to LOADALLDACS
    NEXT
    DAC2V = 0
    GOSUB LOADALLDACS; force envelope ouput on CVout2 to end @ 0	                       	


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

	
SWAPEVENTS:
HIGH STOPLED     

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 DURATION @ RDEX1 with the one @ RDEX2
	    
	    VALW1 = ENVP(RDEX1);>>>>>>>>>>>
	    ENVP(RDEX1) = ENVP(RDEX2);>>>>>
	    ENVP(RDEX2) = VALW1;>>>>>>>>>>> exchange the ENVELOPE BREAKPOINT @ 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

'
'***********************midi**************************
'******************* SUBROUTINES *********************
'*****************************************************
'all hail DB!
#IFNDEF IM_FLAG
	'Poll subroutine for received MIDI
	'Puts data into input buffer and sets RBUFNUM to number of entries
	CHECK_MIDI:
	LET STATUS=SSR
	IF STATUS & %01000000 THEN
		'Data has been received	
		LET MIDIDATA=RDR				'Read Receiver Data Register
		IF MIDIDATA<>$FE THEN			'Ignore if active status
			'Put MIDIDATA into receive buffer
			'Check to see if buffer full
			IF RBUFNUM=RBUFL THEN
				'Buffer is full
				'Simply loose data
			ELSE
				'Buffer has space
				LET RBUF(RPTR_E)=MIDIDATA
				LET RBUFNUM=RBUFNUM+1	'Set RBUFNUM to number of entries
				LET RPTR_E=RPTR_E+1		'Increment pointer for next entry
				IF RPTR_E=RBUFL THEN
					RPTR_E=0			'Wrap pointer at max value
				ENDIF
			ENDIF
		ENDIF
	ELSE
		'No data received
		'Check if error
		IF STATUS & %00111000 THEN
			'Error condition
			LET STATUS=STATUS & %10000111
			LET SSR=STATUS				'Reset error bits
		ENDIF
	ENDIF
	RETURN
#ENDIF
'
'Get MIDIDATA from receive buffer
'RDATAF=1 if successful, RDATAF=0 if no data in buffer
GET_RBUFFER:
	#IFDEF IM_FLAG
		'The interrupt service routine uses the same variables
		'Need to ensure they do not change during this routine
		DISABLE SCI3INT_RDRF
	#ENDIF
	'Check to see if buffer empty
	IF RBUFNUM=0 THEN
		'Buffer is empty
		RDATAF=0						'Set empty flag
	ELSE
		'Buffer has data
		LET MIDIDATA=RBUF(RPTR_S)
		LET RBUFNUM=RBUFNUM-1
		LET RPTR_S=RPTR_S+1
		IF RPTR_S=RBUFL THEN
			LET RPTR_S=0				'Wrap pointer at max value
		ENDIF
		LET RDATAF=1					'Set data returned flag
	ENDIF
	#IFDEF IM_FLAG
		ENABLE SCI3INT_RDRF
	#ENDIF
	RETURN
'
'Subroutine to send MIDIDATA
'Waits until transmitter ready
SEND_MIDI:
	LET STATUS=SSR
	IF STATUS & %10000000 THEN			'Check transmitter ready
		'Ready to transmit	
		LET TDR=MIDIDATA				'Send data to TDR
		RETURN
	ELSE
		GOTO SEND_MIDI
	ENDIF

'*****************************************************
'************ INTERRUPT SERVICE ROUTINES *************
'*****************************************************
'
'Interrupt service routine for timer
CALCTIME:
	LET TIMECOUNT=TIMECOUNT+1			'Increment real time count
	IF TIMECOUNT=TURNOFF THEN			'Value to turn off stopled
		LOW STOPLED
	ENDIF
	IF (TIMECOUNT&$000001FF)=$00000100 THEN
		TOGGLE RUNLED					'Blink run led at 0.512 second intervals
	ENDIF
RESUME
'
'
#IFDEF IM_FLAG
	'Interrupt service routine for received MIDI data
	'Puts data into input buffer and sets RBUFNUM to number of entries
	MIDI_IN_ISR:
	'Do not check SSR
	'Does not function in interrupt mode
	LET I_DATA=RDR						'Read Receiver Data Register
	IF I_DATA<>$FE THEN					'Ignore if active status
	'Check to see if buffer full
		IF RBUFNUM=RBUFL THEN
			'Buffer is full
			'Simply loose data
		ELSE
			'Put data into buffer
			LET RBUF(RPTR_E)=I_DATA
			LET RBUFNUM=RBUFNUM+1		'Set RBUFNUM to number of entries
			LET RPTR_E=RPTR_E+1			'Increment pointer for next entry
			IF RPTR_E=RBUFL THEN
				RPTR_E=0				'Wrap pointer at max value
			ENDIF
		ENDIF
	ENDIF
	RESUME
#ENDIF
'================================================================================================================