Skip to content

VGM structure by simple words (noob's investigation)

Technical discussion which is not directly related to VGM files. Talk about Hardware and Software.

Moderator: Staff

  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

non correct....
Debug Hex(GetOPNNote($13+12, 3335)) shows $FFF and then 3335 shows $800...
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

so! introduce gems case GetOPNNote procedure :)

Code: Select all

Procedure.i GetOPNNote(Note.i, Pitch.w)
  
  Protected.d FreqHz, CurNote, PitchCoef
  Protected.i BlkNum, FNum, CurBlkNum
  
  PitchCoef = Pitch / 256
  If PitchCoef < 1 And PitchCoef > -1
    
    If PitchCoef < 0 And (Note = 12 Or Note = 24 Or Note = 36 Or Note = 48 Or Note = 60 Or Note = 72 Or Note = 84)
      
      Note = Note - 1
      Pitch = 256 + Pitch ;(Pitch is a - value, so 256 + Pitch means 256 - Pitch) 
      
      ret = GetOPNNote(Note, Pitch)
      
    Else
      
      CurNote = Note + Pitch / 256
  
      FreqHz = 440 * Pow(2, (CurNote - 69) / 12)
  
      BlkNum = Note / 12 - 1 ; octave without pitch

      If BlkNum < 0
        BlkNum = 0
      ElseIf BlkNum > 7
        BlkNum = 7
      EndIf  
  
      FNum = Round((144 * FreqHz / 7670454) * Pow(2, 21 - BlkNum), #PB_Round_Nearest)
      If FNum < 0
        FNum = 0
      EndIf 
  
      ;count main value
      ret = FNum + (BlkNum * $800)
      
    EndIf
    
  Else
    
    Note + Pitch / 256
    Pitch - (Int(Pitch / 256) * 256)
    
    ret = GetOPNNote(Note, Pitch)
    
  EndIf
  
  ;set gems limit
  If ret > $3CBF
    ret = $3CBF
  ElseIf ret < $0142
    ret = $0142
  EndIf
  
  ProcedureReturn ret

EndProcedure
it work almost perfect, exept this thing:
GetOPNNote($13+12, 272) shows as $0C02, but GEMS shows as $0C01. for me this $0001 is not very critical and i am no hear this small change in sound, but for perfect match it need to fixed too :) now i have no idea where it is can be fixed.


it is rounding :)

Code: Select all

FNum = Round((144 * FreqHz / 7670454) * Pow(2, 21 - BlkNum), #PB_Round_Down)
round to down if fix problem.

ops. not fix :))) another place now count wrong. need to more tune round procedure :)
  • User avatar
  • ValleyBell Offline
  • Posts: 4767
  • Joined: 2011-12-01, 20:20:07
  • Location: Germany

Post by ValleyBell »

My function handles negative pitch bends perfectly fine, so there should be no reason to check for negative pitch bends.
It also has no problem handling pitch bends that are larger than a semitone. (I ported this from an FM MIDI player I wrote before.)

Also, you didn't port it correctly. (which is, I need to admit, due to weird VB6's syntax I used in that line).
A normal slash (/) is the usual division, but a backslash (\) performs an integer division. (i.e. it discards the decimal part of the result)

Code: Select all

BlkNum = (Note \ 12) - 1
translates into

Code: Select all

BlkNum = Int(Note / 12) - 1
The checks for 0 and $7FF are there to prevent FNum from going out of range and should be left intact. They only ever have an effect if you are at octave/BlkNum 0 and 7.

GEMS uses frequency lookup tables, btw. I don't know how they were generated, but those tables often differ a bit from the actual calculated result.
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

i am late see your post :) by shell's code i make this function, that have tables. values of tables was get by your procedure :))) and it work fine. i check some cases, where my was mistake this new one count fine. and even this new one no need to make +12 for note value. i just get ready for use note and pitch from code files (gems log)

Code: Select all

Global Dim table(12)

table(0) = $0284
table(1) = $02AA
table(2) = $02D3
table(3) = $02FE
table(4) = $032B
table(5) = $035B
table(6) = $038E
table(7) = $03C5
table(8) = $03FE
table(9) = $043B
table(10) = $047B
table(11) = $04BF
table(12) = $0508

Procedure.i GetOPNNote(note.i, pitch.w)
    note_p = pitch / 256    
    pitch = pitch - note_p * 256
    
    If pitch < 0
      note_p = note_p - 1;
      pitch = pitch + 256;
    EndIf 
    
    note = note + note_p
    If note < 0
      pitch = 0;
      note = 0 ;
    ElseIf note > 95
      note = 95
      pitch = 255      
    EndIf
    Block = note / 12
    note = note - Block * 12;
    a = table(note)
    b = table(note+1)
    FNum = a + ((b-a)*pitch)/256 + Block * 2048
    
    ProcedureReturn FNum
    
EndProcedure
i know some case, that will broke this system :) when modulation work - it can have a big values for pitch. but .w type of variable have small limit for it -32768 to +32767. so it can be easy overlimit, that will make wrong count. i will need to make some recount note value for correct count, when this value is overlimit by modulation procedures... it can wait.


now i want to ask question about PSG. i remember your explanations:
1. Attack Phase [volume up With Attack Rate until Attack Level],
2. Decay Phase [volume down With Decay Rate until Sustain Level],
3. Sustain Phase [volume down With SustainRate Until 0]
actually... no, the Sustain Rate is fixed To 0 in GEMS, so the volume
stays at its level there.

Anywhere in this, a "Key Off" breaks into the
4. Release Phase [volume down With Release Rate Until 0]
With "volume 0" I mean "silence" here, so For the PSG it's actually value $F


so can i hope all this talking about same delay 1, as modulation do? i mean for example: Attack Rate = 3, Attack Level = 9 it will means:
delay 1, volume 0, delay 1, volume 3, delay 1, volume 6, delay 1, volume 9, delay 1,...
shell says PSG have 1/60 of second some timer, or whatever it is... but if PSG have his own timer - it is some unlogic and probably will have some asynchronously, becouse this timer is not obey to tempo. but if 1 tik of PSG instrument = 1 delay, as modulation do - no asynchronously and tempo is always same for FM and PSG. i will make as delay 1, and will compare with gems. i will record sound in emulators and read logs for this. but want to know sure :) delay 1 or not delay - that is a question :))))

probably i am never finish all my ideas :))))
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

test shows it is 1/60 sec, not delay 1 :((( Attack Rate $FF, Attack Level $09, Sustain Level $0A

Code: Select all

10000000
11110
0 ch, freq 480
0 ch, vol 9 - work of Attack Rate?
pause
0 ch, vol 9 - Attack Level
pause 
0 ch, vol 10 - Sustain Level? 
pause - hold Sustain Level, until note is key off, or start new note
pause - and this holding is 5 delays duration. it means 3 pauses = 1 delay for this case (40bpm)
pause
pause
pause
pause
pause
pause
pause
pause
pause
pause
pause
pause
pause
10000101
11100
0 ch, freq 453
0 ch, vol 9
pause
it means for PSG instrument work organization i will need additional thread, that will work with 1/60 sec pause in repeat. heh... i thought make all of it in main thread with delay 1 pauses in a repeat.
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

this PSG no have some formula for frequency count from gems note value? or i need to make table of values?

Code: Select all

 note $20	1017
 note $21	1017
 note $22	960
 note $23	906
 note $24	855
 note $25	807
 note $26	762
 note $27	719
 note $28	679
 note $29 	641
 note $2A 	605
 note $2B	571
 note $2C	539
 note $2D	508
 note $2E	480
 note $2F	453
and same with Attack Rate. have it some formulas? or i will need make table for all 31 cases of AR?

Code: Select all

$1F - 31 - 14,  12,  10,  8,  6   4   2   0   0
$1B - 27 - 14,  12,  10,  9,  7,  5,  4,  2,  0,  0
$0A - 10 - 15,  14,  14, 13, 12, 12, 11, 10, 10,  9,  9,  8,  7,  7,  6,  5,  5,  4,  4,  3,  2,  2,  1,  0,  0,  0
(between of values 1/60 sec pause)
  • User avatar
  • ValleyBell Offline
  • Posts: 4767
  • Joined: 2011-12-01, 20:20:07
  • Location: Germany

Post by ValleyBell »

For your first question, I'll just quote the original GEMS source code:

Code: Select all

* fmftbl contains a 16 bit freq number for each half step in a single octave (C-C)

fmftbl		dw	644,682,723,766,811,859,910,965,1022,1083,1147,1215,1288

* psgftbl contains the 16 bit wavelength numbers for the notes A2 thru B7 (33-95)

psgftbl		dw	       03F9H, 03C0H, 038AH	; A2 > B2

		dw	0357H, 0327H, 02FAH, 02CFH	; C3 > B3
		dw	02A7H, 0281H, 025DH, 023BH
		dw	021BH, 01FCH, 01E0H, 01C5H

		dw	01ACH, 0194H, 017DH, 0168H	; C4 > B4
		dw	0153H, 0140H, 012EH, 011DH
		dw	010DH, 00FEH, 00F0H, 00E2H

		dw	00D6H, 00CAH, 00BEH, 00B4H	; C5 > B5
		dw	00AAH, 00A0H, 0097H, 008FH
		dw	0087H, 007FH, 0078H, 0071H

		dw	006BH, 0065H, 005FH, 005AH	; C6 > B6
		dw	0055H, 0050H, 004CH, 0047H
		dw	0043H, 0040H, 003CH, 0039H

		dw	0035H, 0032H, 002FH, 002DH	; C7 > B7 (not very accurate!)
		dw	002AH, 0028H, 0026H, 0023H
		dw	0021H, 0020H, 001EH, 001CH

		dw	001CH				; extra value for interpolation of B7
For your second question, you need to know that ADSR calculations usually work with step values that are added to (attack) or subtracted from (decay/sustain/release) the accumulator value. (That's a system completely different to defining delays between frames.)
An example:
Attack Rate $18 means that the "volume" values for the ticks/frames are: $18, $30, $48, $60, etc. (until the Attack Level is reached)
Also, since PSG volumes are only 4 bits, ADSR usually use 4.4 bit fixed point values for internal calculations. (If you don't know what "fixed point" means, you should look up "fixed-point arithmetic".)

Small note: For basic understanding, you should think of the accumulator value starting at 0 (silence) and go up to $FF (maximum volume) going back down to 0. (see this picture)
However, GEMS seems to use inverted level values (i.e. max. volume = $00, silence = $FF), probably because it matches the PSG's volume scale better. That way is a bit less intuitive though and not as easy to understand, IMO.
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

you are right, i am not understand, as always :) so i am go to long way as Terminator. set notes, compile rom, play at emulator and collect all PSG notes values. and as i can see - it is same as your table for PSG :)

then about attack rate and other this. my investigation shows - all this rate have same mathematiks. it is 1 to 31 values for ways slow up or slow down and 32 value - $FF - when no have this rate and max volume emmidatly. then i start to collect some data - what value of rate make what. and get some "arrays":

Code: Select all

$1F - 31 - 14, 12, 10,  8,  6   4   2   0   0 
$1C - 28 - 14, 12, 10,  8,  7,  5,  3,  1,  0,  0
between values 1/60 sec. when i get a few this "arrays" i am look very carefully and make some formula:

Code: Select all

counterd.d     = 255 / (ARvalue * 15)
then just repeat fill array by values so many time, as int of counterd shows, and 0.x is move to next counterd. when i see arrays - it was very close to original. i was happy - for me it is big victory. but then for fun i remade 15 into 16... unlogic? yes! :) but it works :))) arrays become 100% match :) i am as always not expect that :))) i become more happy, that i was before :)

Code: Select all

Procedure FillARarray(ARvalue.i, ALvalue.i)
  
  ReDim PSGInstARValues(0)
  
  If ARvalue = $FF Or ARvalue = 32; 0 AR, top volume immediatly
    PSGInstARValues(0) = ALvalue
  Else
    counterd.d     = 255 / (ARvalue * 16)
    countershift.d = counterd
    
    For i = 15 To ALvalue Step -1
      
      counteri = Int(countershift)
      
      If counteri > 0
        startnumarray = ArraySize(PSGInstARValues())
        ReDim PSGInstARValues(startnumarray + counteri)
        For k = startnumarray To startnumarray + counteri
          PSGInstARValues(k) = i
        Next
      EndIf
      
      countershift = countershift - counteri ; get 0.x value
      countershift + counterd                ; 0.x + how it names...
      
    Next
    
  EndIf
  
EndProcedure
prototype of this works... not very fine - some times is crash, becouse this array... i very glad to kick out this array and make somehow count values when it plays - hot count, but i have no idea how. so now it is arrays. second problem - i have no idea how to repeat this dinamic allocations of channels for play. now it is support only 1 channel and i make PSG instrument tester:
https://www.dropbox.com/s/qy7q8p7b1qysl ... r.zip?dl=1

no need to load, just make some changes and press play button. and it not support noise type - just tone. what do you think? :)
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

i am still didnt find problem... some how and somewhere some instruments is sets uncorrect. clear to hear it - play 004 track. is some: "tudum" - it is wrong sound.
Attachments
test.zip
(709.16 KiB) Downloaded 252 times
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

problem was in modulation. i thought modulation work only for 1 note, that come after declare modulation, but it plays until damp modulation will declare (00 00 00 modulation file), or probably when instrument is change. this moment about instrument change is stop modulation - need to recheck in tests for sure know.
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

stuck :) samples, modulations and PSG notes and instruments still not done, and i dont khow when i can made it :)
https://www.youtube.com/watch?v=WKjbf65l_Hc
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

probably i am not understand explanation correct... i mean about converting from register into note + pitch values. my code is:

Code: Select all

Global stopflag = 5

Global Dim table(12)

table(0) = $0284
table(1) = $02AA
table(2) = $02D3
table(3) = $02FE
table(4) = $032B
table(5) = $035B
table(6) = $038E
table(7) = $03C5
table(8) = $03FE
table(9) = $043B
table(10) = $047B
table(11) = $04BF
table(12) = $0508

; get note and pitch values from registers
Procedure.i ReturnNoteAndPitch(A4A0.i, reqursivemarker.i=0)
  
  If reqursivemarker
    Debug "reqursive call"
    stopflag - 1
  EndIf
  
  Octave = A4A0 / 2048
  Debug "Octave " + Str(Octave)
  
  Residue = A4A0 - (Octave * 2048)
  Debug "Residue " + Str(Residue)
  
  If Residue < $0284
    Residue * 2 + (2048 * (Octave - 1))
    If stopflag = 0 ; will be removed. now it made stop reqursive, when it stuck   
    Else            ; will be removed 
    tmp = ReturnNoteAndPitch(Residue, 1)
    EndIf           ; will be removed 
  Else
    For i = 11 To 0 Step -1
      If Residue >= table(i)
        note = i
        Break
      EndIf
    Next  
  
    ;from this place i try to revert procedure for get note and pitch
    ;and probably i need to move this part code inside cycle before break
                                 ;FNum = a + ((b-a)*pitch)/256
    A4A0 = Residue - table(note) ;= ((b-a)*pitch)/256  
    A4A0 * 256                   ;= (b-a)*pitch  
    pitchval.f = A4A0 / (table(note+1) - table(note))
    A4A0 = Round(pitchval, #PB_Round_Up)
  
    ;this place is mix 2 values into 1, becouse procedurereturn can have 1 value
    tmp = (note + 12 * Octave) << 16
    tmp + A4A0

  EndIf
  
  ProcedureReturn tmp
  
EndProcedure

;note 11 is $04BF
;note 12 is $0A84
;note 13 is $0AAA

Debug "$0AAA" ; for test
NAP = ReturnNoteAndPitch($0AAA)
Debug "note $" + Hex(NAP >> 16)    ; will show 13
Debug "pitch $" + Hex(NAP & $FFFF) ; will show 0 - all correct

Debug ""

Debug "$0721"
NAP = ReturnNoteAndPitch($0721)
Debug "note $" + Hex(NAP >> 16)    ; will show note $B
Debug "pitch $" + Hex(NAP & $FFFF) ; will show pitch $85C - it is very big pitch value

Debug ""

Debug "$1269"
NAP = ReturnNoteAndPitch($1269)    ; will use reqursive
Debug "note $" + Hex(NAP >> 16)    ; will show note $17
Debug "pitch $" + Hex(NAP & $FFFF) ; will show pitch $43 - looks fine. this value $1269 make problem last time, when you explain how to make recount and from that time it works fine.

Debug ""

Debug "$01C8"
NAP = ReturnNoteAndPitch($01C8)    ; non stop reqursive
Debug "note $" + Hex(NAP >> 16) 
Debug "pitch $" + Hex(NAP & $FFFF)
;debug window content for this case:
;$01C8
;Octave 0
;Residue 456
;reqursive call
;Octave 0
;Residue -1136
;reqursive call
;Octave -2
;Residue -224
;reqursive call
;Octave -3
;Residue -448
;reqursive call
;Octave -4
;Residue -896
;note $0
;pitch $0
so this case have two problem with $0721 and $01C8 - they both lays over notes limit ($0284 to $0508). this $0721 have very big pitch value. can it be recount as $1269 case? problem is $0721 cant to be shift down per 1 octave. and how to correct count $01C8? it is lower, than lower note. i want to curse Jocker - it is his song have this values.
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

unpack arhive - it is Ti_'s Dune compiler. launch DUE file.
set chancel, when he ask path to emulator

VGM2GEMS still not finish, DAC sample detection still not finish, PSG note and instrument detections not finish, slide effect for FM not finish, MIDI2GEMS not pluged too.

sample editor - done
FM instrument editor - almost done, i want to plug .y12 files from KMOD too. now it is GEMS raw and VGM MM vgi support.
PSG player - done
FM player - almost done (DAC samples detection need)
(this editor can be call by right mouse button click on track in a list of Dune's tracks)
Attachments
Dune.zip
(5.81 MiB) Downloaded 253 times
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

ValleyBell, where i have mistake?

when you launch it - it will be empty window. press 5 ch button. it will fill list with registers, then press Play - first time it will be "knock knock" sound. it is wrong. press second time play - it will be play correct, as Alien 3 will shot with grenade launcher.

close programm, open again, but this time press All log and press play. as with 5 ch first time it will be "knock knock", press play second - it will be play as "tone, tone" - not as rocket launcher shot.

what it can be? registers always same. but first time it is one sound, second - another, with full log - even third sound. where can be my mistake?

$22 - no have any $23 and etc per channel - it is always $22. same is $27. same is for $2A and 2B. but for $B0 Feedback, Algorithm it can be $B0 - 1ch, $B1 - 2ch, $B2 - 3ch. $1B0 - 4ch, $1B1 - 5ch, $1B2 - 6ch. correct?

same is for $B4 Left/Right, AMS, FMS

same is for $30 and to $80. but some how command for all channel is change sound for 5 channel. where this mistake can be?


************* later
i see some strange registers: "$33, $0" and "$133, $0". and many other place. but they not change sound. sound change this one:
"$A6, $2C" - why he make effect for 5 channel? it is 6 channel.


************* more later
ValleyBell knows everything :) problem is in a GYM. setting frequency of note must have order: first A4, then A0. but GYM can have first A0, then much later A4. so for fix this problem i heed to write both register, even when comes only one of them.

for example: comes A4 - i need to set both. A4 and 0 for A0. then if comes A0 - i need to set first old A4, then this new value for A0.

i am go to check this :)


************* finish him
it woooooork :) la la la la la
Attachments
opn debug.zip
(36.3 KiB) Downloaded 245 times
  • SeregaZ Offline
  • Posts: 98
  • Joined: 2015-08-08, 13:56:52

Post by SeregaZ »

as you know, i am trying to create special code-player. this code is unpacked GEMS data by splitter programm created by shell. it need to test sound before compilation of rom. without it you will need a lot of clicking :) build GEMS by combaine programm - build rom of Dune - launch it in emulator - wait untill stars is flyaway - move to options - set track for playing. and you will know about one note plays incorrect :) you fix it... and start all actions from begining. it is suks! that is why i am start make this code player - for closer demonstration, how it will be play in emulator.

ValleyBell give OPN.dll - it plays FM part + samples part, Shiru (VGM MM creator) - give PSG core, wilbert is make convert this PSG core to my language of programming. so my pants was a full of happy.
Post Reply