Jump to content

Question about midi_event.inc


Recommended Posts

So, after nearly a year of studying and soldering I've finally got a project that is worth writing some of my own code for (hallucinogen's 808 clone).  I plan on using a modifided MBSeq to trigger the 808 through DOUT pins, saving me the need to build an AOUT which doesn't have enough pins for all the sounds anyway.  Accent will be achieved by sending some voltage level (derived with a simple voltage divider) to all the instruments and using a switch that is also triggered by a DOUT pin (this DOUT module is handy!).  So, in order for this design to work each instrument needs 3 things: a short 5v pulse to trigger it, a switch to turn the accent on/off via another 5v signal and a voltage level for the accent.  Now, as it is the SEQ has just about everything I need, so I really do need to do much work.  All I really need to do is set DOUT pins along with the midi events that are being triggered.  This has two advantages:  it's simple and it allows the sequecer to behave just like a normal MBSeq to external equipment (so I can write basslines right along with the drums ;D). 

Now, after tracing through the source code a bit I've identified midi_event.inc as the file where most of my changes will need to be.  When a midi note is triggered in a drum track I need to first read it's value to determine which instrument it is and then set the appropriate DOUT pin(s).  Here is the code fragment that most interests me:

MIDI_EVNT_Send
	call	MIOS_MIDI_BeginStream

	;; branch depending on MIDI status
	SET_BSR	MIDI_EVNT0
	swapf	MIDI_EVNT0, W, BANKED
	andlw	0x07
	JUMPTABLE_2BYTES_UNSECURE	; 8 entries
	rgoto	MIDI_EVNT_Send_8x	; Note Off
	rgoto	MIDI_EVNT_Send_9x	; Note On
	rgoto	MIDI_EVNT_Send_Ax	; Aftertouch
	rgoto	MIDI_EVNT_Send_Bx	; Controller
	rgoto	MIDI_EVNT_Send_Cx	; Program Change
	rgoto	MIDI_EVNT_Send_Dx	; Channel Pressure
	rgoto	MIDI_EVNT_Send_Ex	; Pitch Bend
	rgoto	MIDI_EVNT_Send_Fx	; AOUT


	;; sending three bytes:	
MIDI_EVNT_Send_8x	; Note Off
MIDI_EVNT_Send_9x	; Note On

Now, aside from the fact that I don't yet understand how the JUMPTABLE implements a switch statement there is something quite suspicious about this code.. see it yet?  The functions for sending note on and off messages are empty!  Surely this cannot be correct so I must not be looking in the right place, no?

Moving on from there for a miniute there is one more dilemma, to trigger the sounds I only need a very short gate signal, meaning that the note off event should be sent almost immediatly after the note on.  Possibly this is easy to do by simply setting the gate length for the drum tracks to a very small value, but I suspect that even the smallest gate length is still a good bit longer than 1ms.  Furthermore this would make it difficult to layer drum sounds from external equipment, so idealy there will be special handling of notes sent to DOUT pins in order to keep the gate length very short.  Possibly I could simply add some simple differetiators in hardware to accomplish this as well, but additonal hardware just seems uneccessary at this point

Link to comment
Share on other sites

Jumping to any of the four of these does the same thing. All of these messages are 3 byte types. You can see 2 byte types next up....

	;; sending three bytes:	
MIDI_EVNT_Send_8x	; Note Off
MIDI_EVNT_Send_9x	; Note On
MIDI_EVNT_Send_Ax	; Aftertouch
MIDI_EVNT_Send_Bx	; Controller
	movff	MIDI_EVNT0, WREG
	call	MIOS_MIDI_TxBufferPut
	movff	MIDI_EVNT1, WREG
	andlw	0x7f
	call	MIOS_MIDI_TxBufferPut
	movff	MIDI_EVNT_VALUE, WREG
	andlw	0x7f
	call	MIOS_MIDI_TxBufferPut
	rgoto	MIDI_EVNT_Send_End

The jumptable explanation will take longer to type so thats the next post

Link to comment
Share on other sites

MIDI_EVNT_Send
	call	MIOS_MIDI_BeginStream

	;; branch depending on MIDI status
	SET_BSR	MIDI_EVNT0
	swapf	MIDI_EVNT0, W, BANKED
	andlw	0x07
	JUMPTABLE_2BYTES_UNSECURE	; 8 entries
	rgoto	MIDI_EVNT_Send_8x	; Note Off
	rgoto	MIDI_EVNT_Send_9x	; Note On
	rgoto	MIDI_EVNT_Send_Ax	; Aftertouch
	rgoto	MIDI_EVNT_Send_Bx	; Controller
	rgoto	MIDI_EVNT_Send_Cx	; Program Change
	rgoto	MIDI_EVNT_Send_Dx	; Channel Pressure
	rgoto	MIDI_EVNT_Send_Ex	; Pitch Bend
	rgoto	MIDI_EVNT_Send_Fx	; AOUT


	;; sending three bytes:	
MIDI_EVNT_Send_8x	; Note Off
MIDI_EVNT_Send_9x	; Note On

OK we start with MIDI_EVNT0 which has the 1st byte in it.

The swapf swaps the low and high nibble, which moves the channel to the 'left' and the midi message to the 'right'. For EG a Note On on channel 5 is 0x94. After the swapf, it's 0x49

The AND 0x07 then clears the channel (that's the 0 in 07) and subtracts 8 from the event type (that's the 7). After this, our example would be 1 (Note it doesn't actually subtract 8, it ANDs with 7, but it has the effect of subtracting 8 )

This effectively makes the jumptable move down 1 extra line and then run the appropriate code.

If the example were a CC on channel 15, the byte would be 0xBE, which would change to 0xEB, then 0x0B and 0x03, so the jumptable causes 3 lines jumped afterwards, which puts it on rgoto MIDI_EVNT_Send_Bx ; Controller

Does that make sense? You might like to take a look at the Seq and MIOS source to see how the jumptable and the MIOS_HLP_GetIndex_2bytes work.

Link to comment
Share on other sites

Thanks stryd, got quite a few AHA!'s in there.  It's going to be some time before I get used to the fact that functions dont return unless you specify it!  Same with how variables are handled, arguments aren't really passed etc..

Good stuff tho, even with the C wrapper available I feel like I get a much better idea of what I'm working with in assembly.

Link to comment
Share on other sites

hey moogah;

Moving on from there for a miniute there is one more dilemma, to trigger the sounds I only need a very short gate signal, meaning that the note off event should be sent almost immediatly after the note on.  Possibly this is easy to do by simply setting the gate length for the drum tracks to a very small value, but I suspect that even the smallest gate length is still a good bit longer than 1ms.  Furthermore this would make it difficult to layer drum sounds from external equipment, so idealy there will be special handling of notes sent to DOUT pins in order to keep the gate length very short.  Possibly I could simply add some simple differetiators in hardware to accomplish this as well, but additonal hardware just seems uneccessary at this point

have you talked to hallucinogen? he doesn't trigger the sounds with the aout; he also uses the dout (running with MIDIO128 but still; dout), he just put in some code so that the douts give ~1ms pulse; could be what you are looking for

cheers

Link to comment
Share on other sites

I got some time to work on this today.  Checking the velocity value was easy, but checking the note numbers was a bit more difficult.  Here is what I've got (with a single accent and only the BD and SD):

	
	movlw 	0x5a 	; If velocity is > 90
	cpfsgt	MIDI_EVNT_VALUE
	MIOS_DOUT_PinSet1(ACCENT_PIN)	; Single level of accent shared by all instruments

	movf	MIDI_ENVT1, 0	; Move note value to W to use ANDL*
        andlw	11101011	; "Binary complement" value for BD (Note#20),
						;  result is 0 when values match (?)
	movwf	SOME_REG	; move W into a register to use DCFSNZ	
	dcfsnz	SOME_REG, 1
	MIOS_DOUT_PinSet1(BD_PIN) ; play BD, pin cleared elsewhere

	andlw	11101010		;" Binary Complement" for SD (#21)			
	movwf	SOME_REG
	dcfsnz	SOME_REG, 1
	MIOS_DOUT_PinSet1(SD_PIN) ; play SD, pin cleared elsewhere

	return

The process of moving the note value into W to use the AND on it and then moving back into an F to check if it is zero seems suspicious to me.  Although even doing all this for all 12 instruments adds less than a 10th of a millisec, by my calculations anyway.  Is there a way to check if W is zero?  Or perform an AND on a f?. I also have the feeling that "Binary Complement" is the wrong term too.

Link to comment
Share on other sites

Thanks guys.  XOR and BZ are what I'm looking for I think.  The datasheet is a bit vague here but I think I can specify a relative branch location, right?  something like PC+2?

got a question right off the bat, how do I identify a binary number?  00010100b?

EDIT:

how about this:

        movf	MIDI_ENVT1, 0	; Move note value to W to use ANDL*
        xorlw	00010100	;  value for BD (Note#20)
        bz      PlayBD         ;  if the result was 0 play BD
PlayBD	MIOS_DOUT_PinSet1(BD_PIN) ; play BD, pin cleared elsewhere

        movf	MIDI_ENVT1, 0	; Move note value to W to use ANDL*
        xorlw	00010101	;  value for BD (Note#20)
        bz      PlaySD         ;  if the result was 0 play SD
PlaySD	MIOS_DOUT_PinSet1(SD_PIN) ; play BD, pin cleared elsewhere
This brings me to another problem.. I had originally tried to use the debugger to get an idea of how the SEQ works, however, it doesn't get any further than the init loop as there is no external stimulus.  Has anyone tried to setup a scenario for the SEQ?  I assume this is not practical, so how have you gone about testing code fragments like this? Also, is there anything like a break statement to skip over the remaining checks? EDIT:  Like this mabey:
CheckBD	movf	MIDI_ENVT1, 0	; Move note value to W to use XORLW
    	xorlw	00010100	;  value for BD (Note#20)
  		bnz		CheckSD         ;  if the result was 0 play BD
		nop
PlayBD	MIOS_DOUT_PinSet1(BD_PIN) ; play BD, pin cleared elsewhere
		goto	MIDI_EVNT_SendAx

CheckSD	movf	MIDI_ENVT1, 0	; Move note value to W to use ANDL*
    	xorlw	00010101	;  value for BD (Note#20)
  		bnz		CheckCH         ;  if the result was 0 play BD
		nop
PlaySD	MIOS_DOUT_PinSet1(SD_PIN) ; play BD, pin cleared elsewhere
		goto	MIDI_EVNT_SendAx
The NOP's are there because, assuming I'm reading the datasheet right, the BNZ command will skip the next line if the Z bit is set (meaning the compare was a match) EDIT: (been a busy day!) I've got a few questions about this USER_SR_Service_Finish function.  Some of the trouble I'm having is due to the fact I can barely understand German, much less tech-speak in German so I'm probably asking stuff that is in Hallucinogen's thread.  It seems that the function has been updated since then as I don't see the call to CS_MENU_TIMER in the snippets from you or Hallucinogen there.  Now, what is causing me trouble is understanding how the Registers work to address the DOUT.  Does each register address one of the 595's?  I see that the MIOS_DOUT_SRSet function sets the SR specified in WREG to the value of MIOS_PARAMETER1.  However, if this is the case the code provided would be clearing nearly every LED on the device, certianly not what we were looking for.  If I understand what is going on I will be addressing SR's 0x04 and 0x05 which clears the first two registers on the second DOUT board
        USER_SR_Service_Finish
        clrf    MIOS_PARAMETER1

        movlw   0x00
        call    MIOS_DOUT_SRSet
        movlw   0x01
        call    MIOS_DOUT_SRSet
        movlw   0x02
        call    MIOS_DOUT_SRSet
        return
        USER_SR_Service_Finish
	;; ---[ handle with control surface variables (flashing cursor, etc) ]---	
	goto	CS_MENU_TIMER

Link to comment
Share on other sites

Ok, new post time.  This is what I've got done today

MIDI_EVNT_Send_9x	; Note On
	movlw 	0x5a 	; If velocity is > 90
	cpfsgt	MIDI_EVNT_VALUE
	MIOS_DOUT_PinSet1(ACCENT_PIN)	; Single level of accent shared by all instruments

CheckCH	movf	MIDI_ENVT1, 0	; Move note value to W to use ANDL*
    	xorlw	CH_NOTE	;  value for CH (Note#20)
  		bnz		CheckRS         ;  if the result was 0 play CH
PlayCH	MIOS_DOUT_PinSet1(CH_PIN) ; play CH, pin cleared elsewhere
		goto	MIDI_EVNT_SendAx


CheckRS	movf	MIDI_ENVT1, 0 ; 
    	xorlw	RS_NOTE
  		bnz		CheckBD        
PlayRS	MIOS_DOUT_PinSet1(RS_PIN) 
		goto	MIDI_EVNT_SendAx


CheckBD	movf	MIDI_ENVT1, 0
    	xorlw	BD_NOTE	
  		bnz		CheckSD        
PlayBD	MIOS_DOUT_PinSet1(BD_PIN) 
		goto	MIDI_EVNT_SendAx


CheckSD	movf	MIDI_ENVT1, 0
    	xorlw	SD_NOTE
  		bnz		CheckCP         
PlaySD	MIOS_DOUT_PinSet1(SD_PIN) 
		goto	MIDI_EVNT_SendAx


CheckCP	movf	MIDI_ENVT1, 0	
    	xorlw	CP_NOTE
  		bnz		CheckOH         
PlayCP	MIOS_DOUT_PinSet1(CP_PIN) 
		goto	MIDI_EVNT_SendAx


CheckOH	movf	MIDI_ENVT1, 0	
    	xorlw	OH_NOTE	
  		bnz		CheckMA        
PlayOH	MIOS_DOUT_PinSet1(OH_PIN) 
		goto	MIDI_EVNT_SendAx


CheckMA	movf	MIDI_ENVT1, 0	
    	xorlw	MA_NOTE
  		bnz		CheckCB         
PlayMA	MIOS_DOUT_PinSet1(MA_PIN) 
		goto	MIDI_EVNT_SendAx


CheckCB	movf	MIDI_ENVT1, 0	
    	xorlw	CB_NOTE
  		bnz		CheckLT         
PlayCB	MIOS_DOUT_PinSet1(CB_PIN) 
		goto	MIDI_EVNT_SendAx


CheckLT	movf	MIDI_ENVT1, 0	
    	xorlw	LT_NOTE
  		bnz		CheckMT         
PlayLT	MIOS_DOUT_PinSet1(LT_PIN) 
		goto	MIDI_EVNT_SendAx


CheckMT	movf	MIDI_ENVT1, 0	
    	xorlw	MT_NOTE
  		bnz		CheckHT         
PlayMT	MIOS_DOUT_PinSet1(MT_PIN) 
		goto	MIDI_EVNT_SendAx


CheckHT	movf	MIDI_ENVT1, 0	
    	xorlw	HT_NOTE
  		bnz		CheckCY        
PlayHT	MIOS_DOUT_PinSet1(HT_PIN) 
		goto	MIDI_EVNT_SendAx


CheckCY	movf	MIDI_ENVT1, 0	
    	xorlw	CY_NOTE
  		bnz		MIDI_EVNT_Send_Ax         
PlayCY	MIOS_DOUT_PinSet1(CY_PIN) 

and
USER_SR_Service_Finish
	;; ---[ handle with control surface variables (flashing cursor, etc) ]---
	clrf    MIOS_PARAMETER1
	movlw   0x04
	call    MIOS_DOUT_SRSet
	movlw   0x05
	call    MIOS_DOUT_SRSet
	goto	CS_MENU_TIMER
Now, aside from being a bit long to look over I think this will run with a minimal impact on the SEQ.  It would be nice if I didn't have to movwf MIDI_ENVT1 each time.  Even so the worst case as I understand it is when a every instrument is triggered at the same time, meaning that around 300 instructions cycles will pass before everything has been triggered(EDIT: this doesn't take into account the number of instructions in each MIOS_DOUT_PinSet call).  This is still only about 1/3 of a millisecond ontop of whatever latencies are already part of the SEQ.  Now, what I need to do is define constants for each DOUT pin and for each note value, that part is fairly easy but a "memory map" for MIOS and the SEQ would be really nice so as to easily find registers that have not been used.  The only question I have at this point is how best to assign a default value to these constants, putting them in USER_Init or somewhere nearby makes sense to me.  There is one thing that has been bugging me about app_defines.h
;;  track record structure
SEQ_TRKSTATEx           EQU     0x00	; flags: see seq_core.inc (SEQ_TRKSTATE_*)
SEQ_TRKMUTED0x          EQU     0x01	; muted steps 1-8
SEQ_TRKMUTED1x          EQU     0x02	; muted steps 9-16
SEQ_TRKPLYTICKx         EQU     0x03	; number of MIDI clock ticks until next step will be played
SEQ_TRKSTEPx            EQU     0x04	; the track step
SEQ_TRKPOSx             EQU     0x05	; the track position
SEQ_TRKQUEUELx          EQU     0x06	; length counter ("note off" once it is zero)
SEQ_D_TRKMUTED_0x	EQU     0x00	; muted steps  1- 4
SEQ_D_TRKMUTED_1x	EQU     0x01	; muted steps  5- 8
SEQ_D_TRKMUTED_2x	EQU     0x02	; muted steps  9-12
SEQ_D_TRKMUTED_3x	EQU     0x03	; muted steps 13-16

SEQ_D_TRKACC_0x		EQU     0x04	; accented steps  1- 4
SEQ_D_TRKACC_1x		EQU     0x05	; accented steps  5- 8
SEQ_D_TRKACC_2x		EQU     0x06	; accented steps  9-12
SEQ_D_TRKACC_3x		EQU     0x07	; accented steps 13-16
this shows different constants being assigned to the same register.. right? EDIT: I found the little note at the bottom of app_defines.h about what address space is left open. 
;;  free for your pleasure: 0x360-0x37f
;;  and 0x380-0x3ff if the AIN handler is disabled

BD_PIN		EQU	0x360
SD_PIN		EQU	0x361
LT_PIN		EQU	0x362
MT_PIN		EQU	0x363
HT_PIN		EQU	0x364
OH_PIN		EQU	0x365
CH_PIN		EQU	0x366
CY_PIN		EQU	0x367
CP_PIN		EQU	0x368
CB_PIN		EQU	0x369
RS_PIN		EQU	0x36a
MA_PIN		EQU	0x36b
ACCENT_PIN	EQU 0x36c

BD_NOTE		EQU	0x36d
SD_NOTE		EQU	0x36e
LT_NOTE		EQU	0x36f
MT_NOTE		EQU	0x370
HT_NOTE		EQU	0x371
OH_NOTE		EQU	0x372
CH_NOTE		EQU	0x373
CY_NOTE		EQU	0x374
CP_NOTE		EQU	0x375
CB_NOTE		EQU	0x376
RS_NOTE		EQU	0x377
MA_NOTE		EQU	0x378

BD_ACCENT		EQU	0x379
SD_ACCENT		EQU	0x37a
LT_ACCENT		EQU	0x37b
MT_ACCENT		EQU	0x37c
HT_ACCENT		EQU	0x37d
OH_ACCENT		EQU	0x37e
CH_ACCENT		EQU	0x37f
CY_ACCENT		EQU	0x380
CP_ACCENT		EQU	0x381
CB_ACCENT		EQU	0x382
RS_ACCENT		EQU	0x383
MA_ACCENT		EQU	0x384

Link to comment
Share on other sites

I assume that the byte you XORLW with will be different for each event yeh?

Have you considered using one of the compare W with F commands? It might save you some code size and allow it to be more modular. That whole repeated code things looks sus....

Edit: won't bother, seeing as I now know about the custom mappings, which disallows a sequential approach

Link to comment
Share on other sites

I'm pretty sure you end up with the same thing that way, once you work it out.  Same goes for modularity, you have to check for all 12 note values, reguardless and you have to branch out when you find a match.  The simplest way is to map all the instruments to sequential note numbers, this way they can be checked in a simple loop.  This loses the ability to make your own mapping, so I don't consider it ideal. 

Link to comment
Share on other sites

The simplest way is to map all the instruments to sequential note numbers, this way they can be checked in a simple loop.  This loses the ability to make your own mapping, so I don't consider it ideal. 

Ahh you didn't mention custom mappings before. Yeh you'll have to do it the way you said above. FWIW the sequential way works out somewhat lighter on codesize and instructions... It's a tradeoff, features vs size/speed, which is pretty typical. I'd stick with they way you've got it so you can include custom mappings.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

×
×
  • Create New...