Page 1 of 1

MIDI DeltaTimes Variables Lenghts Conversions

Posted: Sat Dec 06, 2003 7:37 pm
by Psychophanta
As a result of conversation with einander i found it interesting and i made it:

Code: Select all

; English forum: http://www.purebasic.fr/english/viewtopic.php?t=8639&highlight=
; Author: Psychophanta
; Date: 06. December 2003
; OS: Windows
; Demo: Yes


; MIDI DeltaTimes Variables Lenghts Conversions 
; MIDI DeltaTimes - Konvertieren von Varaiblenlängen

; Esta es la explicación: 
; 
;Los Delta Times son omnipresentes en los MIDI files. Indican el tiempo entre notas y la longitud de los strings. 
;Es un truquito muy interesante. 

; A delta-time is stored as a series of bytes which is called a variable length quantity. 
; Only the first 7 bits of each byte is significant (right-justified; sort of like an ASCII byte). 
; So, If you have a 32-bit delta-time, you have To unpack it into a series of 7-bit bytes 
; (ie, as If you were going To transmit it over midi in a SYSEX message). 
; Of course, you will have a variable number of bytes depending upon your delta-time. 
; To indicate which is the last byte of the series, you leave bit #7 clear. 
; In all of the preceding bytes, you set bit #7. 
; So, If a delta-time is between 0-127, it can be represented as one byte. 
; The largest delta-time allowed is 0FFFFFFF, which translates To 4 bytes variable length. 
; Here are examples of delta-times as 32-bit values, And the variable length quantities that they translate To: 
; 
; 
;  NUMBER        VARIABLE QUANTITY 
; 00000000              00 
; 00000040              40 
; 0000007F              7F 
; 00000080             81 00 
; 00002000             C0 00 
; 00003FFF             FF 7F 
; 00004000           81 80 00 
; 00100000           C0 80 00 
; 001FFFFF           FF FF 7F 
; 00200000          81 80 80 00 
; 08000000          C0 80 80 00 
; 0FFFFFFF          FF FF FF 7F 
Procedure.L ReadVarLen(PO.l) 
  !mov ebx,dword[esp];<-cargo en ebx el valor en cuestión 
  !mov cl,24 
  !shrd eax,ebx,7;desplazo el valor en 7 bit a la derecha, entrándole por la izquierda a eax desde ebx 
  !shr eax,1;<-desplazo 1 bit a la derecha metiéndo un 0 por su izquierda 
  !shr ebx,7; 
  !jz @f 
  !sub cl,8 
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx 
  !shr eax,1;<-desplazo 1 bit a la derecha 
  !bts eax,31;<-dejando un 1 en su izquierda 
  !shr ebx,7 
  !jz @f 
  !sub cl,8 
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx 
  !shr eax,1;<-desplazo 1 bit a la derecha 
  !bts eax,31;<-dejando un 1 en su izquierda 
  !shr ebx,7 
  !jz @f 
  !shrd eax,ebx,7;desplazo a la derecha últimos 7 bit. 
  !shr eax,1;<-desplazo 1 bit a la derecha 
  !bts eax,31;<-dejando un 1 en su izquierda 
  ProcedureReturn 
  !@@:shr eax,cl 
  ProcedureReturn 
EndProcedure 

Procedure WriteVarLen(x) 
  !mov ebx,dword[esp];<-cargo en ebx el valor en cuestión 
  !mov cl,25 
  !shrd eax,ebx,7;desplazo el valor en 7 bit a la derecha, entrándole por la izquierda a eax desde ebx 
  !shr ebx,8 
  !;jc error  ;<-for error detection 
  !jz @f 
  !sub cl,7 
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx 
  !shr ebx,8 
  !;jnc error  ;<-for error detection 
  !jz @f 
  !sub cl,7 
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx 
  !shr ebx,8 
  !;jnc error  ;<-for error detection 
  !jz @f 
  !sub cl,7 
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx 
  !;shr ebx,8   ;<-needed for error detection 
  !;jnc error  ;<-for error detection 
  !@@:shr eax,cl 
  ProcedureReturn 
EndProcedure 

;TEST 
A.L=$FABADA 
B.L = ReadVarLen(A) 
Debug "Original Hex="+Hex(A) 
Debug "Transformado en Variable Length= "+Hex(B) 
Debug " Y reconvertido a Hex :" +Hex(WriteVarLen(B))

Posted: Sun Dec 07, 2003 11:46 pm
by einander
Psychophanta: you example is fine! :D

Here is another one, more musical focused, to read byte streams, and getting the pointer position for MIDIfile parsing.
I'm using strings :cry: , but may be you can transform this in another ASMarvel :!:

Code: Select all

; ReadVarLenData

Global Forward.L ; keeps pointer positiono 
Procedure BSet(A, N) ; Sets the N bit 
ProcedureReturn A | 1 << N
EndProcedure

Procedure BClR(A, N) ; Clears the N bit
ProcedureReturn BSet(A, N) ! BSet(0, N)
EndProcedure

Procedure BTST(A, N) ; Returns state of N bit
If A & BClr(A, N) = A : ProcedureReturn 0 : EndIf
ProcedureReturn 1
EndProcedure

Procedure Bin_Dec(bin$) ; Binary to decimal
Le = Len(bin$) 
For i = 0 To Le-1
a = Val(Mid(bin$, le - i, 1))
If a = 1
b + Pow(2, i)
EndIf
Next
ProcedureReturn(b)
EndProcedure

Procedure ReadVarLenData(PO) ;' ' returns value ot the Variable Length found from PO - keeps in Forward the number of  bytes in  VarLen
Repeat
PokeB(@by,PeekB(PO)& $FF)
PO +1
a$=RSet(Bin(by),8,"0")   ; very bad use of strings, sure Psycho found a better way... 
c$ + Mid(a$,2,Len(a$))
Until Btst(by,7) = 0
Forward=Po ; New pointer position
ProcedureReturn bin_dec(c$)
EndProcedure

; TESTING ==============================
PO=AllocateMemory(0, 30, 0)
; Some Variable Length with defferent number of bytes, to test
; Pointer gets the new value from Forward
PokeB(po,$FF) : PokeB(po+1,$FF) :PokeB(po+2,$FF) : PokeB(po+3,$7F)
PokeB(po+4,$C0) :PokeB(po+5,$80) : PokeB(po+6,$80) : PokeB(po+7,$00)
PokeB(po+8,$81) : PokeB(po+9,$80) : PokeB(po+10,$80) :PokeB(po+11,$00)
PokeB(po+12,$FF) : PokeB(po+13,$FF) : PokeB(po+14,$7F)
PokeB(po+15,$C0) : PokeB(po+16,$80) : PokeB(po+17,$00)
PokeB(po+18,$81) : PokeB(po+19,$80) : PokeB(po+20,$00)
PokeB(po+21,$FF) : PokeB(po+22,$7F)
PokeB(po+23,$C0) : PokeB(po+24,$00)
PokeB(po+25,$81) : PokeB(po+26,$00)
PokeB(po+27,$7F) : PokeB(po+28,$40)
PokeB(po+29,$00)

p=PO
Repeat
Debug Hex(ReadVarlenData(PO)) +" -- "+Str(Forward-PO)+" Bytes forward"
PO=Forward
Until po>=P+30
Regards
Einander

Posted: Mon Dec 08, 2003 12:51 am
by Psychophanta
Oufffh! your code is very "twisted":
PokeB(@by,PeekB(PO)& $FF) :?: 8O
Better is: "by=PeekB(PO)&$FF"

Here, your functions for bit managements, simplified:

Code: Select all

Procedure BSet(A, N) ; Sets the N bit
    ProcedureReturn A|1<<N
EndProcedure

Procedure BClR(A, N) ; Clears the N bit
    ProcedureReturn A&~(1<<N)
EndProcedure

Procedure BTST(A, N) ; Returns state of N bit
    ProcedureReturn A>>N&1
EndProcedure
and just when i went to email it to you, better i put it here. All your above code is resumed in:

Code: Select all

; ReadVarLenData

Global *Forward ; is the current pointer 

Procedure.l ReadVarLenDataASM(*PO);<-returns value found in *PO, and save in *Forward the current mem pointer position
  !mov edi,dword[p.p_PO]
  !XOr eax,eax
  !mov bl,byte[edi];<-cargo en bl el valor en cuestión
  !inc edi;<-incremento puntero
  !shl ebx,25
  !jnc ReadVarLenDataASMsal
  !@@:
  !shld eax,ebx,7
  !mov bl,byte[edi];<-cargo en bl el valor en cuestión
  !inc edi
  !shl ebx,25
  !jc @r
  !ReadVarLenDataASMsal:
  !mov dword[p_Forward],edi;<-*Forward must be a global, either put "Shared *Forward" inside this function
  !shld eax,ebx,7
  ProcedureReturn
EndProcedure 

*PO=AllocateMemory(30)

; Several Varlen with different lenghts to prove
; Pointer is going forward as indicate *Forward
PokeB(*PO,$FF):PokeB(*PO+1,$FF):PokeB(*PO+2,$FF):PokeB(*PO+3,$7F)
PokeB(*PO+4,$C0):PokeB(*PO+5,$80):PokeB(*PO+6,$80):PokeB(*PO+7,$00)
PokeB(*PO+8,$81):PokeB(*PO+9,$80):PokeB(*PO+10,$80):PokeB(*PO+11,$00)
PokeB(*PO+12,$FF):PokeB(*PO+13,$FF):PokeB(*PO+14,$7F)
PokeB(*PO+15,$C0):PokeB(*PO+16,$80):PokeB(*PO+17,$00)
PokeB(*PO+18,$81):PokeB(*PO+19,$80):PokeB(*PO+20,$00)
PokeB(*PO+21,$FF):PokeB(*PO+22,$7F)
PokeB(*PO+23,$C0):PokeB(*PO+24,$00)
PokeB(*PO+25,$81):PokeB(*PO+26,$00)
PokeB(*PO+27,$7F)
PokeB(*PO+28,$40)
PokeB(*PO+29,$00)
*P=*PO

Repeat
Debug Hex(ReadVarlenDataASM(*PO)) +" LONG: "+Str(*Forward-*PO)
*PO=*Forward
Until *PO>=*P+30
Well, perhaps it is more interesting to have the pointer where reading is finished (instead of Avanza variable just is a byte counter); if so, tell me.

By the way, in PB there are 2 nice commands named Read and Data. Why don't you use it? :wink:

Posted: Mon Dec 08, 2003 3:14 am
by einander
Great Psycho! Thanks!
By the way, in PB there are 2 nice commands named Read and Data. Why don't you use it? :wink:
Because I dont' have the Manual! :lol: And there are things that I dont know that I dont know!

I have a mess with the names to Write and Read:
Can we use Write to transform from Decimal to Variable length, and Read to transform from Variable Length to Decimal. (Or vice versa? :twisted: )

Posted: Mon Dec 08, 2003 3:28 am
by einander
Well, perhaps it is more interesting to have the pointer where reading is finished (instead of Avanza variable just is a byte counter); if so, tell me.


I can't imagine a condition where is needed the pointer value before the Variable length is finished, so it's OK to have the pointer value as you said.
I've renamed Avanza as Forward, honoring our fellows english MIDI coders.

Posted: Mon Dec 08, 2003 3:23 pm
by Psychophanta
einander wrote
I can't imagine a condition where is needed the pointer value before the Variable length is finished, so it's OK to have the pointer value as you said.
Then, updated (and simplified) code in my last reply.
EDIT on 2007-12-23: Updated my codes here to PB 4.10 8)

Re: MIDI DeltaTimes Variables Lenghts Conversions

Posted: Sat Jul 30, 2016 12:57 pm
by Joris
Hi,

Can anyone check this old post ?
I'm not into ASM, but I don't get the result of the WriteVarLen(x) procedure.
I would think that WriteVarLen(B) would result in the same value as started created by B=ReadVarLen(A).
A more expanded/explained sample would be nice (test).

Thanks.
;TEST
A.L=$FABADA
B.L = ReadVarLen(A)
Debug "Original Hex="+Hex(A)
Debug "Transformado en Variable Length= "+Hex(B)
Debug " Y reconvertido a Hex :" +Hex(WriteVarLen(B))

Re: MIDI DeltaTimes Variables Lenghts Conversions

Posted: Sun Aug 21, 2016 3:00 pm
by Psychophanta
Joris wrote:Hi,

Can anyone check this old post ?
I'm not into ASM, but I don't get the result of the WriteVarLen(x) procedure.
I would think that WriteVarLen(B) would result in the same value as started created by B=ReadVarLen(A).
A more expanded/explained sample would be nice (test).

Thanks.
;TEST
A.L=$FABADA
B.L = ReadVarLen(A)
Debug "Original Hex="+Hex(A)
Debug "Transformado en Variable Length= "+Hex(B)
Debug " Y reconvertido a Hex :" +Hex(WriteVarLen(B))
Dear,
the ASM is commented in spanish, so you have no problem to understand it using translation. :wink:

Thank you!

Re: MIDI DeltaTimes Variables Lenghts Conversions

Posted: Sun Aug 21, 2016 5:49 pm
by Joris
The translation doesn't explain very much...

If I compare the WriteVarLen(x) procedure to the code below there is a big difference. Of course in coding style, but I mean the final result should somehow become the same. I don't see how that should happen with the ASM version. Too me the ASM version looks as a part of what the complete thing. It seems only the NumToVLD part, but as I said I'm not into ASM and not sure.

Thanks.

Code: Select all

Procedure NumToVLD (v) ; Variable lenght Datas encoder
  ct = 0
  rv = 0
  While v
    l1 = v&$7F
    v = (v - l1)/$80
    If ct > 0
      l1+ $80
    EndIf
    ct2 = ct : While ct2 : l1*256 : ct2 - 1 : Wend
    rv + l1
    ct + 1
  Wend
  ProcedureReturn rv
EndProcedure
Procedure WriteVLD (fhdn.i, v)
  vo = NumToVLD (v)
  ct = 3
  While PeekB(@vo+ct)=0 : ct - 1 : Wend
  While ct>=0
    v = PeekB(@vo+ct) : If v<0 : v + 256 : EndIf
    ;WriteByte(fhdn,v)    ;normaly this does the output (to a file or memory block)
    Debug Hex(v)          ; this now just to show the number of bytes and there values
    ct - 1
  Wend
EndProcedure 
;**********************************
WriteVLD(adres,$FABADA)

Re: MIDI DeltaTimes Variables Lenghts Conversions

Posted: Mon Aug 22, 2016 12:06 pm
by Psychophanta
Ok, in order for you to get it, i will try tomorrow to do the same purpose of that function in PB.

Re: MIDI DeltaTimes Variables Lenghts Conversions

Posted: Tue Aug 23, 2016 2:56 pm
by Psychophanta
Psychophanta wrote:Ok, in order for you to get it, i will try tomorrow to do the same purpose of that function in PB.
Here you have it:

Code: Select all

; Esta es la explicación:
;
;Los Delta Times son omnipresentes en los MIDI files. Indican el tiempo entre notas y la longitud de los strings.
;Es un truquito muy interesante.

; A delta-time is stored as a series of bytes which is called a variable length quantity.
; Only the first 7 bits of each byte is significant (right-justified; sort of like an ASCII byte).
; So, If you have a 32-bit delta-time, you have To unpack it into a series of 7-bit bytes
; (ie, as If you were going To transmit it over midi in a SYSEX message).
; Of course, you will have a variable number of bytes depending upon your delta-time.
; To indicate which is the last byte of the series, you leave bit #7 clear.
; In all of the preceding bytes, you set bit #7.
; So, If a delta-time is between 0-127, it can be represented as one byte.
; The largest delta-time allowed is 0FFFFFFF, which translates To 4 bytes variable length.
; Here are examples of delta-times as 32-bit values, And the variable length quantities that they translate To:
;
;
;  NUMBER        VARIABLE QUANTITY
; 00000000              00
; 00000040              40
; 0000007F              7F
; 00000080             81 00
; 00002000             C0 00
; 00003FFF             FF 7F
; 00004000           81 80 00
; 00100000           C0 80 00
; 001FFFFF           FF FF 7F
; 00200000          81 80 80 00
; 08000000          C0 80 80 00
; 0FFFFFFF          FF FF FF 7F
Procedure.l WriteVarLenNOASM(x.l)
  ebx.l=x; <- cargo en ebx el valor en cuestión
  cl.b=24
  eax.l=0
  eax>>7:eax&$01FFFFFF:eax|((ebx<<25)&$FE000000); !shrd eax,ebx,7; desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  eax>>1:eax&$7FFFFFFF            ; !shr eax,1; <- desplazo 1 bit a la derecha metiéndo un 0 por su izquierda
  ebx>>7:ebx&$01FFFFFF            ; !shr ebx,7; <- desplazo 7 bit a la derecha metiéndo 0 por su izquierda
  If ebx=0
    For t.b=1 To cl
      eax>>1:eax&$7FFFFFFF
    Next
    ProcedureReturn eax
  EndIf
  cl-8
  eax>>7:eax&$01FFFFFF:eax|((ebx<<25)&$FE000000); !shrd eax,ebx,7; desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  eax>>1:eax|$80000000            ; !shr eax,1; <- desplazo 1 bit a la derecha metiéndo un 1 por su izquierda
  ebx>>7:ebx&$01FFFFFF            ; !shr ebx,7; <- desplazo 7 bit a la derecha metiéndo 0 por su izquierda
  If ebx=0
    For t.b=1 To cl
      eax>>1:eax&$7FFFFFFF
    Next
    ProcedureReturn eax
  EndIf
  cl-8
  eax>>7:eax&$01FFFFFF:eax|((ebx<<25)&$FE000000); !shrd eax,ebx,7; desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  eax>>1:eax|$80000000            ; !shr eax,1; <- desplazo 1 bit a la derecha metiéndo un 1 por su izquierda
  ebx>>7:ebx&$01FFFFFF            ; !shr ebx,7; <- desplazo 7 bit a la derecha metiéndo 0 por su izquierda
  If ebx=0
    For t.b=1 To cl
      eax>>1:eax&$7FFFFFFF
    Next
    ProcedureReturn eax
  EndIf
  eax>>7:eax&$01FFFFFF:eax|((ebx<<25)&$FE000000); !shrd eax,ebx,7; desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  eax>>1:eax|$80000000            ; !shr eax,1; <- desplazo 1 bit a la derecha metiéndo un 1 por su izquierda
  ProcedureReturn eax
EndProcedure
Procedure.l WriteVarLen(PO.l)
  !mov ebx,dword[p.v_PO];<-cargo en ebx el valor en cuestión
  !mov cl,24
  !shrd eax,ebx,7;desplazo el valor en 7 bit a la derecha, entrándole por la izquierda a eax desde ebx
  !shr eax,1;<-desplazo 1 bit a la derecha metiéndo un 0 por su izquierda
  !shr ebx,7;
  !jz @f
  !sub cl,8
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  !shr eax,1;<-desplazo 1 bit a la derecha
  !bts eax,31;<-dejando un 1 en su izquierda
  !shr ebx,7
  !jz @f
  !sub cl,8
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  !shr eax,1;<-desplazo 1 bit a la derecha
  !bts eax,31;<-dejando un 1 en su izquierda
  !shr ebx,7
  !jz @f
  !shrd eax,ebx,7;desplazo a la derecha últimos 7 bit.
  !shr eax,1;<-desplazo 1 bit a la derecha
  !bts eax,31;<-dejando un 1 en su izquierda
  ProcedureReturn
  !@@:shr eax,cl
  ProcedureReturn
EndProcedure
Procedure.l ReadVarLen(x.l)
  !mov ebx,dword[p.v_x];<-cargo en ebx el valor en cuestión
  !mov cl,25
  !shrd eax,ebx,7;desplazo el valor en 7 bit a la derecha, entrándole por la izquierda a eax desde ebx
  !shr ebx,8
  !jc error  ;<-for error detection
  !jz @f
  !sub cl,7
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  !shr ebx,8
  !jnc error  ;<-for error detection
  !jz @f
  !sub cl,7
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  !shr ebx,8
  !jnc error  ;<-for error detection
  !jz @f
  !sub cl,7
  !shrd eax,ebx,7;desplazo a la derecha otros 7 bit, los cuales entran por la izquierda desde ebx
  !shr ebx,8   ;<-needed for error detection
  !jnc error  ;<-for error detection
  !@@:shr eax,cl
  ProcedureReturn
  !error:
  MessageRequester("error in the delta-time variable","")
EndProcedure
;TEST
A.L=$FABADA
B.L = WriteVarLenNOASM(A)
Debug "Original Hex="+Hex(A)
Debug "Transformado en Variable Length= "+Hex(B)
Debug " Y reconvertido a Hex :" +Hex(ReadVarLen(B))

Re: MIDI DeltaTimes Variables Lenghts Conversions

Posted: Tue Aug 23, 2016 5:42 pm
by wilbert
@Joris,
If you want to write directly to a file, I noticed some code online that does it more or less like this

Code: Select all

Procedure WriteVLD (fhdn, v)
  While v >= $80
    WriteByte(fhdn, v|$80)
    v >> 7
  Wend
  WriteByte(fhdn, v)
EndProcedure
If you want to output a number like Psychophanta, ASM is the fastest solution.

Re: MIDI DeltaTimes Variables Lenghts Conversions

Posted: Wed Aug 24, 2016 8:41 am
by Joris
@Psychophanta thanks, but it doesn't help me much further. Sorry to say but I don't understand how I should use it.

@Wilbert thanks, to me that looks as the right setup.