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
so! introduce gems case GetOPNNote procedure
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
round to down if fix problem.
ops. not fix )) another place now count wrong. need to more tune round 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
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)
ops. not fix )) another place now count wrong. need to more tune round procedure
- ValleyBell Offline
- Posts: 4804
- Joined: 2011-12-01, 20:20:07
- Location: Germany
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)translates into
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.
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
Code: Select all
BlkNum = Int(Note / 12) - 1
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.
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)
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 )))
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
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 )))
test shows it is 1/60 sec, not delay 1 (( Attack Rate $FF, Attack Level $09, Sustain Level $0A
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.
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
this PSG no have some formula for frequency count from gems note value? or i need to make table of values?
and same with Attack Rate. have it some formulas? or i will need make table for all 31 cases of AR?
(between of values 1/60 sec pause)
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
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
- ValleyBell Offline
- Posts: 4804
- Joined: 2011-12-01, 20:20:07
- Location: Germany
For your first question, I'll just quote the original GEMS source code:
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.
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
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.
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":
between values 1/60 sec. when i get a few this "arrays" i am look very carefully and make some formula:
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
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?
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
Code: Select all
counterd.d = 255 / (ARvalue * 15)
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
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?
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 264 times
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.
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
https://www.youtube.com/watch?v=WKjbf65l_Hc
probably i am not understand explanation correct... i mean about converting from register into note + pitch values. my code is:
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.
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
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)
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 266 times
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
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 255 times
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.
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.