I disassembled the event handler for 0xE0 and 0xE3 and try understanding them. Here is my note for assembly code of 0xE0 handler (0xE3 is quite similar)
/*
Event Handler Parameter:
R0 event sequance pointer
R2 "Track Controller Structure" pointer
R3 "Audio Output Structure?" pointer
R3+0x18 (the final volume?)written by both event E0 and E3
R3+0x2C (the volume?)written by event E0(=parameter<<16), read by event E3
R3+0x34 (the volume?)written by event E0(=parameter<<16)
R3+0x38 cleared by event E0
R3+0x50 (the expression?) read by event E0, written by event E3(=parameter)
R3+0xB4 (a linked list head pointer?) read by both event E0 and E3
R3+0xC4 (a pointer, to the main audio output controller?) read by both event E0 and E3
*/
============================================================================
0207227C(Event E0 Handler)
0207227C STMDB SP!,{R3-R5,LR}
02072280 LDRSB R4,[R0,#0]//r4=parameter (as signed byte)
02072284 MOV R5,#0//r5=0
02072288 LDR R2,[02072308]//r2=0x82061029(whats this...)
0207228C MOV R1,R4,LSL #10//r1=r4<<0x10
02072290 STR R1,[R3,#34]//*(u32*)(r3+0x34)=r1(=parameter<<16)(store the track valume)
02072294 STR R1,[R3,#2C]//*(u32*)(r3+0x2C)=r1(=parameter<<16)(also store the track valume?)
02072298 STRH R5,[R3,#38]//*(u16*)(r3+0x38)=r5(=0)
0207229C LDR R12,[R3,#C4]//r12=*(u32*)(r3+0xC4) (= pointer to main audio controller?)
020722A0 LDRB R1,[R3,#50]//r1=*(u8*)(r3+0x50)( =track expression)
020722A4 LDRSB LR,[R12,#8]//LR=*(s8*)(r12+8) ( =main volume? it's often 0x7F in memory and decrease its value when the bgm fades out)
020722A8 LDR R12,[0207230C]//r12=0x04000208(=Register_IME)
//The following code looks like mixing the track volume ,expression and main volume together
//But by a quite strange method
//Note: now r1=track_expression, r4=track_volume, lr=main_volume?
020722AC SMULBB R1,R4,R1//r1*=r4
020722B0 MUL R4,LR,R1//r4=LR*r1
020722B4 SMULL R1,LR,R2,R4//(u64)(r1,LR)=r2*r4?
020722B8 ADD LR,R4,LR//LR+=r4
020722BC MOV R1,R4,LSR #1F//r1=r4>>0x1F
020722C0 ADD LR,R1,LR,ASR #D//LR=r1+(LR>>0xD)
020722C4 STRH LR,[R3,#18]//*(u16*)(r3+0x18)=LR (store final volume?)
020722C8 LDRH R4,[R12,#0]//r4=*(u16*)0x04000208(Get current IRQs state)
020722CC STRH R5,[R12,#0]//*(u16*)0x04000208=r5(=0)(Disable IRQs)
020722D0 LDR R2,[R3,#B4]//r2=*(u32*)(r3+0xB4)
//r2 looks like a linked list pointer
020722D4 CMP R2,#0
020722D8 BEQ 020722F4 //while(r2!=0){
//This loop looks like notifying every members in the list
020722DC LDRH R1,[R2,#6]//r1=*(u16*)(r2+6)
020722E0 ORR R1,R1,#20//r1|=0x20
020722E4 STRG R1,[R2,#6]//*(u16*)(r2+6)=r1
020722E8 LDR R2,[R2,#154]//r2=*(u16*)(r2+0x154)
020722EC CMP R2 #0
020722F0 BNE 020722DC //}
020722F4 LDR R2,[0207230C]//r2=0x04000208
020722F8 ADD R0,R0,#1//R0+=1 (r0 is the event sequance pointer)
020722FC LDRH R1,[R2,#0] r1=*(u16*)0x04000208
02072300 STRH R4,[R2,#0] *(u16*)0x04000208=r4(Restore IRQs state)
02072304 LDMIA SP!,{R3-R5,PC}//return
As you can see, the volume envelope staff looks strange, computing with a magic number 0x82061029.
I think maybe it is just "final_volume=track_volume*expression*main_volume/something;", which is in the most common way. Because ARM doesn't support division directly, it computes like that.
But if it is not the correct way to envelop the volume, I have no more idea at present.