Noob's investigation of VGM and DMF and SMD audio drivers

For everything that's not in any way related to PureBasic. General chat etc...
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

wilbert, have you idea how to correct count bytes, from that it need to get frequency value for second chip? as i understand it can be 3 cases:
1. value lays x0C adress of file. and it is .l type. if this value lower, than $40000000 - it means chip is one and used this frequency.
2. value bigger, than $40000000. it means value from file - $40000000 = this will be frequency for each chips.
3. same case, that 2 have, but file have extra header, where sets frequency for second chip. (or maybe not for second, but probably can both)

probably it have 4 case:
Note: Bit 31 (0x80000000) is used on combination with the dual-chip-bit to indicate that this is a T6W28. (PSG variant used in Neo Geo Pocket)
so problem is - how to get that extra header value :) it write on wiki a little confuse for me.


Joris, no, VGM have a lot of chips. my Mulder's investigation have only part of VGM universe: that have Sega Mega Drive II - YM2612 and SN76489. wilbert make this convert for SN. YM was made by ValleyBell and as dll, not PB. i have sourse, but it is too big code - i will shoot my self if try to convert, i even cant correct convert SN :) and they plays both fine. for me fine :) maybe they have some asynchronously, but main idea for my program it is convert VGM to back in game Dune (and maybe in a future to other GEMS audio driver games). this player it is only the tip of the iceberg. before work of wilbert - i have only YM sound playing. tracks sounds not full, but it will be better, than nothing. but now i have full house :)

send code happen by command "Write" from module of wilbert. value for send get from VGM files (that part of VGM, that have SN chip). and then sends by timer from array (will be better make play at same time, when player start read file, but now it is read in array step - long step, then play array. for SN only - read happen fast, but for YM it takes long time.)
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

i am make brain explosion of ValleyBell by my stuped questions, but probably i something understand:
Image
Last edited by SeregaZ on Sat Sep 24, 2016 2:40 pm, edited 1 time in total.
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

cant to start engine :((((

Image
Last edited by SeregaZ on Sat Sep 24, 2016 2:41 pm, edited 1 time in total.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Noob's investigation of VGM

Post by wilbert »

@SeregaZ, my ISP seems to be blocking the domain where you put your images.
I can't see them so I don't know what you are talking about :(

The extra header can be found by looking at the header offset (address 0xBC of the VGM file).
I'll probably try to convert the ActionScript based parser as well somewhere next week. It provides sample accurate playback without timing issues.
(your delay approach works pretty well on Windows but is not accurate enough on Mac).

@Joris, it's about emulating the sound chips and playing music on those emulated chips.
I didn't know about it until I encountered this thread but I like the relative simplicity of the SN76489.
It's really '80s and the cpu load of the emulated chip is < 1% at my computer.
Windows (x64)
Raspberry Pi OS (Arm64)
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

i change images host. it work?
PureBasic.asm[1532]
mov[edx + 4 + rcx],eax
error: invalid address
(PB 5.31, x86, XP)
wilbert wrote: your delay approach works pretty well on Windows but is not accurate enough on Mac.
try to play with:

Code: Select all

PlayedUS = PlayedTicks * 22.675736961;90.702947844;22.675736961
this 22.675736961 i count from... it need to remember...

delay have millisec, but i need microsec. VGM plays as 44100 in 1 sec. 1 sec have 1000 millisec. so 1 delay it is a little more, than 1/44100. that is why need microsec. so i somehow get this 22.67blablabla from that millisec...

Debug 44100 * 22.675736961
and it is almost 1 sec :)))) that is how i get that.
Last edited by SeregaZ on Sat Sep 24, 2016 4:03 pm, edited 1 time in total.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Noob's investigation of VGM

Post by wilbert »

SeregaZ wrote:i change images host. it work?
PureBasic.asm[1532]
mov[edx + 4 + rcx],eax
error: invalid address
(PB 5.31, x86, XP)
Yes, I can see the images now. :)
The error should be fixed; small change.
Windows (x64)
Raspberry Pi OS (Arm64)
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

it is play, no error - but gaps return. and second chip now plays that was must be play first. probably that is why gaps happen - they eat each other.

i set frequency correct?
o.l = PeekL(*FileMem+$0C)
Clock(o)
set that big value from normal header, then check extraheader if this extra header have some value for 0 chip - get that value and set as for second:
Clock(extrafrequency, 1)

ops! my fault :)))) write is changes too :)))



about extra - i make some ugly way for get that value... ugly as always i am do :)))

Code: Select all

;{
   ProgName.s=GetFilePart(ProgramFilename()) 
   a = CreateSemaphore_(#Null,0,1,@ProgName) 
   If a<>0 And GetLastError_()=#ERROR_ALREADY_EXISTS 
     CloseHandle_(a) 
     End 
   EndIf
;} 
   
Enumeration
  
  #Window
  
  #PathString
  #OpenButton
  #File
  
  #Text
  
  #Fr01
  #Fr02
  
  
  #Ch1R
  #Ch2R
  #Ch3R
  #Ch4R
  #Ch1L
  #Ch2L
  #Ch3L
  #Ch4L
  
  
  #Ch1R2
  #Ch2R2
  #Ch3R2
  #Ch4R2
  #Ch1L2
  #Ch2L2
  #Ch3L2
  #Ch4L2
  
  #Play
  #Stop
  #From
  
EndEnumeration 

XIncludeFile "SN76489 module.pb"

Structure VGMFSt
  type.i
  reg.a
  val.u
  pause.u
  samplenum.a
  sampleadress.i
  samplesize.i
  summofpauses.i
EndStructure
Global Dim VGMARR.VGMFSt(0)

Global TormozFlag = 1
Global PlThr

;{
Macro SetBit(Var, Bit)
  Var | (Bit)
EndMacro
 
Macro ClearBit(Var, Bit)
  Var & (~(Bit))
EndMacro

Macro TestBit(Var, Bit)
  Bool(Var & (Bit))
EndMacro
 
Macro NumToBit(Num)
  (1<<(Num))
EndMacro

Macro GetBits(Var, StartPos, EndPos)
  ((Var>>(StartPos))&(NumToBit((EndPos)-(StartPos)+1)-1))
EndMacro
;}

Procedure Play(*Value)
  
  PlayedTicks.i = 0
  PlayedUS.i = 0
  CurrentUS.i = 0
  StartMS.i = ElapsedMilliseconds()
  
  start = Val(GetGadgetText(#From))

  For i = start To ArraySize(VGMARR())-1

    Select VGMARR(i)\type
      Case 3 ; pauses    
        PlayedTicks + VGMARR(i)\pause
        PlayedUS = PlayedTicks * 22.675736961;90.702947844;22.675736961
        While (CurrentUS < PlayedUS)
          Delay(1)
          CurrentUS.i = (ElapsedMilliseconds() - StartMS) * 1000
        Wend

      Case 5 ; PSG
        Write(VGMARR(i)\val)
        
      Case 6 ; PSG
        Write(VGMARR(i)\val, 1)
        
      Case 7 ; stereo flags
        GGStereoWrite(VGMARR(i)\val)
        
      Case 8 ; stereo flags
        GGStereoWrite(VGMARR(i)\val, 1)
        
    EndSelect
    
    If TormozFlag      
      Break
    EndIf 
    
  Next
  
  ;silence when stop
  Write(%10011111)
  Write(%10111111)
  Write(%11011111)
  Write(%11111111)
  
  Write(%10011111, 1)
  Write(%10111111, 1)
  Write(%11011111, 1)
  Write(%11111111, 1)
  
EndProcedure

Procedure ParsePlay(*FileMem)
  
  memsize = MemorySize(*FileMem)  

  ;get version of vgm. ugly code? :)))
  ver$ = ""
  tmp = PeekA(*FileMem + 11)
  ver$ = Str(GetBits(tmp, 4, 7))
  ver$ + Str(GetBits(tmp, 0, 3))
  tmp = PeekA(*FileMem + 10)
  ver$ + Str(GetBits(tmp, 4, 7))
  ver$ + Str(GetBits(tmp, 0, 3))
  tmp = PeekA(*FileMem + 9)
  ver$ + Str(GetBits(tmp, 4, 7))
  ver$ + Str(GetBits(tmp, 0, 3))
  tmp = PeekA(*FileMem + 8)
  ver$ + Str(GetBits(tmp, 4, 7))
  ver$ + Str(GetBits(tmp, 0, 3))
  
  o.l = PeekL(*FileMem+$0C)  
  Clock(o) ; set clock per track. idk will it make effect, or not...
  ;Debug "frequency is " + Str(o)
  
  If Val(ver$) < 151
    ot = *FileMem + 64      ; 64 - it is vgm header. no need it yet
  Else
    If Val(ver$) < 171
      ot = *FileMem + 192   ; 192 header from 1.51 to 1.70
    Else
      ot = *FileMem + 256   ; 256 header from 1.71
    EndIf
    
    ;+extrahead
    If PeekL(*FileMem+$BC) = 4
      ot + PeekL(*FileMem+$C0) ; extra head data size
      
      ;check frequency values
      If o > $40000000 ; it means dual and need to check extra
        shift = PeekL(*FileMem+$C4) ; offset of "Chip Clock" sub header
        If shift
          chipcount = PeekB(*FileMem+$C4+shift) ; to know how many 5 bytes blocks have
          If chipcount
            readfrom = *FileMem+$C4+shift + 1
            For i = 1 To chipcount
              If PeekB(readfrom) = 0 ; 0 - it is id of SN
                extrafrequency = PeekL(readfrom + 1)
                Debug extrafrequency ; f..king!!!!!!!1111oneoneone
                If extrafrequency
                  Clock($40000000 + extrafrequency, 1)
                EndIf
                Break
              EndIf
              readfrom + 5
            Next
          EndIf
        EndIf
      EndIf
      
    EndIf
    
  EndIf  
  do = *FileMem + memsize

  ; create array with size as filesize. array will get less size, than file, but it is ok
  Dim VGMARR(memsize)

  Number.a = 0
  PSGvalue.a
  Arrayind = 0  
  
  For i = ot To do
    
    Number = PeekA(i)
    
    Select Number      
        
      Case $67  ;  0x67 0x66 tt ss ss ss ss (data)
        ; it is big wav data block        
        ; get size of wav data block
        WavDataSize = PeekI(i + 3)
        ; get adress of wav data block
        WavAddres = i + 7
        ; jump to end of wav data block
        i + 6 + WavDataSize 
        
      Case $52 ; $52 - register +0
        i+2
        
      Case $53 ; $53 - register +256
        i+2 
        
      Case $61 ; $61 - can range from 0 to 65535 (approx 1.49 seconds)
        VGMARR(Arrayind)\type = 3
        VGMARR(Arrayind)\pause = PeekU(i + 1)
        If flagpausehunt
          VGMARR(numforsizecount)\summofpauses + VGMARR(Arrayind)\pause
          flagpausehunt = 0
        EndIf
        Arrayind + 1
        i+2
        
      Case $70 To $7F ; wait n+1 samples, n can range from 0 to 15.
        VGMARR(Arrayind)\type = 3
        VGMARR(Arrayind)\pause = ((Number - $70) + 1)
        If flagpausehunt
          VGMARR(numforsizecount)\summofpauses + VGMARR(Arrayind)\pause
          flagpausehunt = 0
        EndIf
        Arrayind + 1
        
      Case $E0 ; 0xE0	dddddddd	seek to offset dddddddd (Intel byte order) in PCM data bank
        ;jump far
        i + 4
        
      Case $80 To $8F
        flagpausehunt = 1        
        If Number > $80
          VGMARR(Arrayind)\type = 3
          VGMARR(Arrayind)\pause = (Number - $80)
          VGMARR(numforsizecount)\summofpauses + VGMARR(Arrayind)\pause
          Arrayind + 1
        EndIf
        
      Case $4F ; 0x4F	dd	Game Gear PSG stereo, write dd to port 0x06 ; dune with samples
        i + 1
        PSGvalue = PeekA(i)
        VGMARR(Arrayind)\type = 7
        VGMARR(Arrayind)\val = PSGvalue
        Arrayind + 1        
        
      Case $3F ; 0x4F	dd	Game Gear PSG stereo, write dd to port 0x06 ; dune with samples
        i + 1
        PSGvalue = PeekA(i)
        VGMARR(Arrayind)\type = 8
        VGMARR(Arrayind)\val = PSGvalue
        Arrayind + 1        
        
      ;MAIN COMMAND :))
      Case $50 ; 0x50	dd	PSG (SN76489/SN76496) write value dd ; dune with samples
        i + 1
        PSGvalue = PeekA(i)
        VGMARR(Arrayind)\type = 5
        VGMARR(Arrayind)\val = PSGvalue
        Arrayind + 1
        
      Case $30 ; 0x50	dd	PSG (SN76489/SN76496) write value dd ; dune with samples
        i + 1
        PSGvalue = PeekA(i)
        VGMARR(Arrayind)\type = 6
        VGMARR(Arrayind)\val = PSGvalue
        Arrayind + 1  
        
        
      Case $62 ; wait 735 samples (60th of a second), a shortcut for 0x61 0xdf 0x02 ; Lego Tune
        VGMARR(Arrayind)\type = 3
        VGMARR(Arrayind)\pause = 735
        If flagpausehunt
          VGMARR(numforsizecount)\summofpauses + VGMARR(Arrayind)\pause
          flagpausehunt = 0
        EndIf
        Arrayind + 1
        
      Case $63 ; wait 882 samples (50th of a second), a shortcut For 0x61 0x72 0x03
        VGMARR(Arrayind)\type = 3
        VGMARR(Arrayind)\pause = 882
        If flagpausehunt
          VGMARR(numforsizecount)\summofpauses + VGMARR(Arrayind)\pause
          flagpausehunt = 0
        EndIf
        Arrayind + 1
        
      Case $66 ; end of sound
        Debug "end"        
        Break 
        
      Default 
        ;Debug "unknown command " + Hex(Number)
        
        
    EndSelect
    
    If TormozFlag
      Break
    EndIf

  Next
  
  If TormozFlag = 0
    Play(0)
  EndIf
  
EndProcedure

#ENABLE_GZIP = 16
; #ZLIB_VERSION = "1.2.8"

#Z_NULL = 0
#Z_OK = 0
#Z_STREAM_END = 1
#Z_FINISH = 4
#Z_BLOCK         =  5

Structure Z_STREAM
  *next_in.Byte
  avail_in.l
  total_in.l
  *next_out.Byte
  avail_out.l
  total_out.l
  *msg.Byte
  *state
  zalloc.l     
  zfree.l  
  opaque.l
  
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    PB_Alignment1.b[4]
  CompilerEndIf
  
  data_type.i
  adler.l
  reserved.l
  
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    PB_Alignment2.b[8]
  CompilerEndIf  
EndStructure

ImportC "zlib.lib"
  zlibVersion()
  inflateInit2_(*strm, windowBits.i, Version.s, strm_size)
  inflate(*strm, flush.i)
  inflateEnd(*strm)
  deflateInit2_(*strm, level.i, method.i, windowBits.i, memlevel.i, strategy.i, Version.s, strm_size)  
  deflateBound(*strm, sourceLen.l)
  deflate(*strm, flush.i)
  deflateEnd(*strm)
EndImport : Global ZLIB_VERSION$ = PeekS(zlibVersion(), -1, #PB_Ascii)

Procedure InflatePayload(*TmpMem, windowBits.i, size.i)
  Debug "запустилось"
  LengthToRead = MemorySize(*TmpMem)
  LengthToWrite = size;FileSize("1test.dds")
  *MemoryID = AllocateMemory(LengthToWrite)
  strm.Z_STREAM
  strm\next_in = *TmpMem
  strm\avail_in = LengthToRead
  strm\next_out = *MemoryID
  strm\avail_out = LengthToWrite
  strm\zalloc = #Z_NULL
  strm\zfree = #Z_NULL
  strm\opaque = #Z_NULL
  inflateInit2_(@strm, windowBits, ZLIB_VERSION$, SizeOf(Z_STREAM))
  inflate(@strm, #Z_FINISH)
  inflateEnd(@strm)
  ProcedureReturn *MemoryID
EndProcedure




If OpenWindow(#Window, 100, 200, 195, 260, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
  
  StringGadget(#PathString, 10, 20, 120, 20, "", #PB_String_ReadOnly)
  ButtonGadget(#OpenButton, 130, 20, 50, 20, "open")
  
  TextGadget(#Text, 10, 40, 170, 90, "WARNING! see what chips was used by this VGM. it plays only one SN76489. YM2612 is ignored. any VGM with other chips can crash programm.")
  
  StringGadget(#From, 10, 120, 65, 20, "550")
  GadgetToolTip(#From, "starts from...")
  
  ButtonGadget(#Play, 80, 120, 50, 20, "play")
  ButtonGadget(#Stop, 135, 120, 50, 20, "stop")
  
  FrameGadget(#Fr01, 1, 158, 93, 97, "1 chip")
  FrameGadget(#Fr02, 95, 158, 93, 97, "2 chip")
  
  
  CheckBoxGadget(#Ch1L, 5, 170, 70, 20, "L   1Ch   R")
  CheckBoxGadget(#Ch2L, 5, 190, 70, 20, "L   2Ch   R")
  CheckBoxGadget(#Ch3L, 5, 210, 70, 20, "L   3Ch   R")
  CheckBoxGadget(#Ch4L, 5, 230, 70, 20, "L   4Ch   R")
  
  CheckBoxGadget(#Ch1R, 75, 170, 16, 20, "")
  CheckBoxGadget(#Ch2R, 75, 190, 16, 20, "")
  CheckBoxGadget(#Ch3R, 75, 210, 16, 20, "")
  CheckBoxGadget(#Ch4R, 75, 230, 16, 20, "")
  
  CheckBoxGadget(#Ch1L2, 100, 170, 70, 20, "L   1Ch   R")
  CheckBoxGadget(#Ch2L2, 100, 190, 70, 20, "L   2Ch   R")
  CheckBoxGadget(#Ch3L2, 100, 210, 70, 20, "L   3Ch   R")
  CheckBoxGadget(#Ch4L2, 100, 230, 70, 20, "L   4Ch   R")
  
  CheckBoxGadget(#Ch1R2, 170, 170, 16, 20, "")
  CheckBoxGadget(#Ch2R2, 170, 190, 16, 20, "")
  CheckBoxGadget(#Ch3R2, 170, 210, 16, 20, "")
  CheckBoxGadget(#Ch4R2, 170, 230, 16, 20, "")
  
 
  
  
  Repeat
    
    Event = WaitWindowEvent()
    
    Select Event

      Case #PB_Event_Gadget
        Select EventGadget()
          ;- Event  
          Case #OpenButton
            If File$
              StandardFile$ = GetPathPart(File$)
            Else
              StandardFile$ = "C:\" ;GetPathPart(ProgramFilename());         ; initial path + file
            EndIf
            Pattern$ = "VGM files (*.vgm)|*.vgm;*.vgz;";*.vgz|"   ; set first pattern  (index = 0)
            Pattern = 0    ; use the second of the five possible patterns as standard

            ; Now we open a filerequester, you can change the pattern and will get the index after closing
            File$ = OpenFileRequester("Please choose file to load", StandardFile$, Pattern$, Pattern)
            If File$
              ;For i = #Ch1R To #Ch4L2
              ;  SetGadgetState(i, 1)
              ;Next
              ;GGStereoWrite(255)
              ;GGStereoWrite(255 + 256)              
              SetGadgetState(#Ch2R2, 1)              
              SetGadgetState(#Ch2L2, 1)
              GGStereoWrite(0)
              GGStereoWrite(34, 1)
              
              TormozFlag = 1
              If IsThread(PlThr)
                WaitThread(PlThr)
              EndIf
              SetGadgetText(#PathString, File$)
              
              If ReadFile(#File, File$)
                
                If *MemoryID
                  FreeMemory(*MemoryID)
                  *MemoryID = 0
                EndIf
                
                length = Lof(#File)
                
                If ReadByte(#File) = $56 And ReadByte(#File) = $67 And ReadByte(#File) = $6D
                  ;unpacked VGM 
                  Debug "unpacked"
                  
                  *MemoryID = AllocateMemory(length)         ; allocate the needed memory
                  If *MemoryID
                    FileSeek(#File, 0)
                    ReadData(#File, *MemoryID, length)   ; read all data into the memory block
                  EndIf
                Else                  
                  Debug "packed"
                  ;get size unpacked
                  FileSeek(#File, length - 4)
                  unpsz = ReadLong(#File)
                  
                  If *TmpMem
                    FreeMemory(*TmpMem)
                    *TmpMem = 0
                  EndIf
                  
                  *TmpMem = AllocateMemory(length)         ; allocate the needed memory
                  If *TmpMem
                    FileSeek(#File, 0)
                    ReadData(#File, *TmpMem, length)   ; read all data into the memory block
                    
                    *MemoryID = InflatePayload(*TmpMem, #ENABLE_GZIP, unpsz) ;unpack  
                    
                  EndIf
                  
                EndIf                  

                CloseFile(#File)
                
                TormozFlag = 0
                
                If *MemoryID
                  PlThr = CreateThread(@ParsePlay(), *MemoryID)
                Else
                  MessageRequester("error", "cant read file")
                EndIf                
                
              EndIf
              
            EndIf
            
          Case #Ch1R To #Ch4L
            forstereo = 0
            For i = #Ch1R To #Ch4L
              value = GetGadgetState(i)
              If value
                SetBit(forstereo, NumToBit(i-#Ch1R))
              Else
                ClearBit(forstereo, NumToBit(i-#Ch1R))
              EndIf
            Next
            GGStereoWrite(forstereo)
            
          Case #Ch1R2 To #Ch4L2
            forstereo = 0
            For i = #Ch1R2 To #Ch4L2
              value = GetGadgetState(i)
              If value
                SetBit(forstereo, NumToBit(i-#Ch1R2))
              Else
                ClearBit(forstereo, NumToBit(i-#Ch1R2))
              EndIf
            Next
            GGStereoWrite(forstereo, 1)
            
          Case #Play
            If *MemoryID
              TormozFlag = 1
              If IsThread(PlThr)
                WaitThread(PlThr)
              EndIf
              
              TormozFlag = 0
              PlThr = CreateThread(@Play(), *MemoryID)
              
            EndIf
            
          Case #Stop
            TormozFlag = 1
            
            
        EndSelect
            
            

      Case #PB_Event_CloseWindow
         Quit = 1
         
    EndSelect

  Until Quit = 1
  
EndIf

End 
he is not like when i sets 2 000 000 value.
Clock(extrafrequency, 1)

probably he want this one:
Clock($40000000 + extrafrequency, 1)
that is work :))))) now 1 in 1 sounds as winamp do... for this cases... who knows where ValleyBell set grand piano in a bush...
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Noob's investigation of VGM

Post by wilbert »

The VGM specification also mentions Chip Volume Header.
Do you understand it ?
It's not very clear to me how it should affect the output of the chip.
Windows (x64)
Raspberry Pi OS (Arm64)
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

ValleyBell says it used for all chip and SN76489 too, but he not have examples of VGM for this case. if we no have examples - how we know how right it sounds?
Chip Volume Header

1 byte - Entry Count (chips with user-defined volumes)
[4 bytes - List Entry 1]
[4 bytes - List Entry 2]
...
Each list entry has the format:

1 byte - Chip ID (chip order follows the header)

Note: If bit 7 is set, it's the volume for a paired chip. (e.g. the AY-part of the YM2203)
1 byte - Flags

Note: If bit 0 is set, it's the volume for the second chip.
2 bytes - volume for the chip

Note: If Bit 15 is 0, this is an absolute volume setting. If Bit 15 is 1, it's relative and the chip volume gets multiplied by ((Value & 0x7FFF) / 0x0100).


i am vote for ignore it :) study black hole by watching them in telescope is suks.

i see this as additional value for volume table:
\vt[0] = 4095 * somevalue : \vt[1] = 4095 * somevalue : \vt[2] = 4095 * somevalue
\vt[3] =

where this somevalue = 1 for basic, and some 1.1 for louder, 0.9 for lower... but how to get logic - i dont know :) ValleyBell not give answer for this case. as i say - grand piano in a bush :) i think this extra volume values is more rare, then PSG with samples.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Noob's investigation of VGM

Post by wilbert »

I got a new version you can try; rewrote a lot so it would need some testing.

Code: Select all

; SN76489 module v1.43 by Wilbert
; modeled after ActionScript code by Shiru

DeclareModule SN76489
  
  Declare SN76489()
  Declare SetChipVolume(volume.u, chip = 0)
  Declare SetClock(freq.l, chip = 0)
  Declare SetNoisePattern(pattern.l, chip = 0)
  Declare Reset(chip = 0)
  Declare Render(*buf, len.l)
  Declare GGStereoWrite(val.a, chip = 0)
  Declare Write(val.a, chip = 0)
  
EndDeclareModule

Module SN76489
  
  EnableExplicit
  DisableDebugger  
  EnableASM
  
  ;- Structures
  
  Structure Chip      ; 100 bytes
    ticksCount.l      ; offset  0
    ticksPerSample.l  ; offset  4
    vol.a[4]          ; offset  8
    div.u[4]          ; offset 12
    cnt.w[4]          ; offset 20
    out.u[4]          ; offset 28
    noiseLFSR.u       ; offset 36
    noiseTap.u        ; offset 38
    flipflop.a[4]     ; offset 40
    channel_mix.l[4]  ; offset 44
    lpfFeedback.u[4]  ; offset 60
    volume.u          ; offset 68
    noiseRegShift.u   ; offset 70
    latchedChan.u     ; offset 72
    latchedVol.u      ; offset 74
    reserved.u[12]    ; offset 76
  EndStructure
  
  Structure SN76489_State
    bufpos.l          ; [-16432]
    buftotal.l[2]     ; [-16428]
    buffer.l[4096]    ; [-16420]
    vt.u[16]          ; [   -36]   volume table
    flags.l           ; [    -4]   bit 31 = T6W28, bit 30 = dual chip
    chip.Chip[2]      ; [     0]
  EndStructure
    
  ;-Global variables
  
  Global SN76489_State.SN76489_State
  
  ;- Render core
  
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    Macro rax : eax : EndMacro
    Macro rbx : ebx : EndMacro
    Macro rcx : ecx : EndMacro
    Macro rdx : edx : EndMacro
    Macro rsi : esi : EndMacro
    Macro rdi : edi : EndMacro
  CompilerEndIf  
  
  Macro M_Channel(chip, channel)
    sub word [rsi+20 + chip*100 + channel*2], 1     ; cnt - 1
    !jnc .c1#chip#channel                           ; cnt >= 0 => c#channel
    movzx ecx, word [rsi+12 + chip*100 + channel*2] ; get div
    CompilerIf channel = 3
      ; >> noise <<
      !and ecx, 3
      !mov eax, 0x10
      !shl al, cl
      !jns .n#chip
      movzx eax, word [rsi+12 + chip*100 + 2*2]     ; get div of channel 2
      !.n#chip:
      !shl eax, 1
      mov [rsi+20 + chip*100 + channel*2], ax       ; set cnt
      movzx eax, word [rsi+36 + chip*100]           ; noiseLFSR
      movzx edx, word [rsi+38 + chip*100]           ; noiseTap
      movzx ecx, word [rsi+70 + chip*100]           ; noise reg width
      !and edx, eax                                 ; apply noiseTap to noiseLFSR
      !xor dl, dh                                   ; get parity
      !setnp dl
      !shl edx, cl
      !shr eax, 1
      !or eax, edx
      mov [rsi+36 + chip*100], ax                   ; update noiseLFSR
      !and eax, 1
      !jz .c0#chip#channel
      movzx ecx, byte [rsi+8 + chip*100 + channel]  ; get noise vol
    CompilerElse
      ; >> tone <<
      !xor eax, eax
      mov [rsi+20 + chip*100 + channel*2], cx       ; set cnt
      !cmp ecx, 1                                   ; div = 1 ?
      !je .c0#chip#channel
      movzx ecx, byte [rsi+8 + chip*100 + channel]  ; get vol
      !jb .t#chip#channel
      movzx eax, byte [rsi+40 + chip*100 + channel] ; get flipflop
      !or ecx, eax
      !xor eax, 0xf                                 ; invert
      mov [rsi+40 + chip*100 + channel], al         ; set flipflop
      !.t#chip#channel:  
    CompilerEndIf
    movzx eax, word [rsi-36 + rcx*2]                ; lookup vol in table
    movzx ecx, word [rsi+68 + chip*100]             ; combine with chip volume
    !imul eax, ecx
    !shr eax, 8
    !.c0#chip#channel:
    mov [rsi+28 + chip*100 + channel*2], ax         ; set out value
    !.c1#chip#channel:
    movzx eax, word [rsi+28 + chip*100 + channel*2] ; low pass filter
    movzx ecx, word [rsi+60 + chip*100 + channel*2]
    !lea eax, [eax*3]
    !lea ecx, [ecx*5]
    !add eax, ecx
    !shr eax, 3
    mov [rsi+60 + chip*100 + channel*2], ax
  EndMacro
  
  Macro M_Tick(chip)
    sub dword [rsi + chip*100], 0x01000000          ; update ticksCount
    !ja .rloop#chip
    mov eax, [rsi+4 + chip*100]                     ; ticksCount+=ticksPerSample
    add [rsi + chip*100], eax
  EndMacro
    
  Macro M_ChannelOut(chip, channel)
    movzx ecx, word [rsi+60 + chip*100 + channel*2]
    imul ecx, [rsi+44 + chip*100 + channel*4]       ; apply channel mix
    !add eax, ecx
  EndMacro
    
  Procedure Render(*buf, len.l)
    !mov ecx, [p.v_len]
    !test ecx, ecx
    !jz .exit
    mov rax, *buf
    mov rdx, SN76489_State
    push rbx
    push rsi
    push rdi
    lea rsi, [rdx+16432]                            ; point to first chip 
    mov ebx, [rsi-4]
    mov rdi, rax
    !and ecx, 0x3fffffff
    !or ebx, ecx
    !.rloop0:
    M_Channel(0,0)
    M_Channel(0,1)
    M_Channel(0,2)
    M_Channel(0,3)
    M_Tick(0)
    !bt ebx, 30
    !jnc .rskip0 
    !.rloop1:
    M_Channel(1,0)
    M_Channel(1,1)
    M_Channel(1,2)
    M_Channel(1,3)
    M_Tick(1)
    !.rskip0:
    !xor eax, eax
    M_ChannelOut(0,0)
    M_ChannelOut(0,1)
    M_ChannelOut(0,2)
    M_ChannelOut(0,3)
    !bt ebx, 30
    !jnc .rskip1
    M_ChannelOut(1,0)
    M_ChannelOut(1,1)
    M_ChannelOut(1,2)
    M_ChannelOut(1,3)
    !shr eax, 1                                     ; divide by 2 if 2 chips
    !and eax, 0x7fff7fff
    !.rskip1:
    mov ecx, [rsi-16432]                            ; get and update buffer index
    !add ecx, 1
    !and ecx, 4095
    mov [rsi-16432], ecx
    mov edx, [rsi-16420 + rcx*4]                    ; remove old buffer sample
    mov [rsi-16420 + rcx*4], eax                    ; write new buffer sample
    !sub dx, ax
    !movsx ecx, dx
    !or edx, 0xffff
    !sub edx, eax
    !sar edx, 16
    sub [rsi-16428], ecx                            ; update totals
    sub [rsi-16424], edx
    mov edx, [rsi-16428]                            ; >> process left channel <<
    !shr edx, 12
    !movzx ecx, ax
    !sub ecx, edx
    !shl ecx, 2
    !cmp ecx, 32767
    !jng .clip0
    !mov ecx, 32767
    !.clip0:
    mov [rdi], cx                                   ; write left output sample
    mov edx, [rsi-16424]                            ; >> process right channel <<
    !shr edx, 12
    !shr eax, 16
    !sub eax, edx
    !shl eax, 2
    !cmp eax, 32767
    !jng .clip1
    !mov eax, 32767
    !.clip1:
    mov [rdi+2], ax                                 ; write right output sample
    add rdi, 4
    !sub ebx ,1
    !test ebx, 0x3fffffff
    !jnz .rloop0
    pop rdi
    pop rsi
    pop rbx
    !.exit:
  EndProcedure
  
  ;- Other procedures
  
  Procedure SetChipVolume(volume.u, chip = 0)
    volume & $7fff : If volume > $400 : volume = $400 : EndIf
    SN76489_State\chip[chip]\volume = volume
  EndProcedure
      
  Procedure SetClock(freq.l, chip = 0)
    !mov eax, [p.v_freq]
    !test eax, eax
    !jz .clock_exit
    !and eax, 0x003fffff
    !cdq
    !mov ecx, 0x01000000
    !mul ecx
    !mov ecx, 16 * 44100
    !div ecx
    !mov ecx, [p.v_chip]
    !and ecx, 1
    !imul ecx, 100
    mov rdx, SN76489_State
    add rdx, 16432                                  ; point to first chip
    mov [rdx+4 + rcx], eax
    !and ecx, ecx
    !jnz .clock_exit
    !mov eax, [p.v_freq]
    !and eax, 0xc0000000
    mov [rdx-4], eax
    !.clock_exit:
  EndProcedure
  
  Procedure SetNoisePattern(pattern.l, chip = 0)
    SN76489_State\chip[chip]\noiseTap = pattern
    SN76489_State\chip[chip]\noiseRegShift = pattern >> 16 - 1
  EndProcedure
    
  Procedure Reset(chip = 0)
    Protected i.i
    With SN76489_State\chip[chip]
      \ticksCount = \ticksPerSample
      For i = 0 To 3
        \vol[i] = 15 : \div[i] = 1 : \cnt[i] = 0 : \out[i] = 0
        \flipflop[i] = 0 : \channel_mix[i] = $00010001 : \lpfFeedback[i] = 0
      Next
      SetNoisePattern($100009, chip)
      \noiseLFSR = 1 << \noiseRegShift : \div[3] = 0
      \volume = $100 : \latchedChan = 0 : \latchedVol = 0
    EndWith
  EndProcedure
  
  Procedure SN76489()
    Protected i.i, v.d = 2047
    FillMemory(@SN76489_State, SizeOf(SN76489_State))
    For i = 0 To 14 : SN76489_State\vt[i] = v : v * 0.79432823 : Next
    SetClock(3579545) : SetClock(3579545, 1)
    Reset() : Reset(1)
  EndProcedure
  
  Procedure Write(val.a, chip = 0)
    Protected.l chan, cdiv
    chip & 1
    With SN76489_State\chip[chip]
      If val & $80
        chan = val >> 5 & 3
        \latchedChan = chan
        \latchedVol = val >> 4 & 1
        cdiv = (\div[chan] & $3f0) | (val & $f)
      Else
        chan = \latchedChan
        cdiv = (\div[chan] & $f) | (val << 4 & $3f0)
      EndIf
      If \latchedVol
        \vol[chan] = val & $f
      Else
        \div[chan] = cdiv
        If chan = 3
          \noiseTap = cdiv << 1 & 8 + 1
          \noiseLFSR = $8000      
        EndIf
      EndIf
    EndWith
  EndProcedure
  
  Procedure GGStereoWrite(val.a, chip = 0)
    Protected.l i
    chip & 1
    For i = 0 To 3
      SN76489_State\chip[chip]\channel_mix[val >> 6 & 4 + i] = (val >> (i+4) & 1) | (val << (16-i) & $10000)
    Next
  EndProcedure
    
EndModule
Last edited by wilbert on Sat Oct 01, 2016 2:19 pm, edited 9 times in total.
Windows (x64)
Raspberry Pi OS (Arm64)
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

0% cpu eating. all plays fine.
Last edited by SeregaZ on Tue Sep 27, 2016 10:16 am, edited 1 time in total.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Noob's investigation of VGM

Post by wilbert »

I posted an update with a new procedure SetMasterVolume.
http://www.purebasic.fr/english/viewtop ... 43#p494943
In general you should be able to use 1.5 without getting distortion but you can experiment for yourself.
Windows (x64)
Raspberry Pi OS (Arm64)
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

SetMasterVolume(volume.f = 1.0) - it no have chip select?
SetMasterVolume(volume.f = 1.0, chip = 0) i think it need to be some like this
Last edited by SeregaZ on Tue Sep 27, 2016 9:15 pm, edited 2 times in total.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Noob's investigation of VGM

Post by wilbert »

SeregaZ wrote:SetMasterVolume(volume.f = 1.0) - it no have chip select?
SetMasterVolume(volume.f = 1.0, chip = 0) i think it need to be some like this
The master volume is done after both chips have been mixed which is easier. Setting the chip volume is more complicated.
I can add it but only for a limited range (for example 0.0x - 2.0x) which seems to be incompatible with the VGM additional volume header (I prefer it to be compatible).
It looks like the VGM volume header allows a multiplication in the range of 0.0 - 128.0. :?
I have no idea why you would want such a range.
Windows (x64)
Raspberry Pi OS (Arm64)
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Noob's investigation of VGM

Post by SeregaZ »

this extra volume for PSG probably is not exists. and about chip splitting:
Note: If bit 0 is set, it's the volume for the second chip.
so first chip can be default, second can be some tuned. that is why i am talk about it.

about 128 - probably we can make some custom VGM file, and see what winamp will do. but i think PSG no have so big values. if PSG have any values for this volume :)

https://www.dropbox.com/s/owrb3x0ge4h75 ... t.vgm?dl=1

i dont know what i must hear, but i am hear nothing :)
xD6 when it have 0F FF value. or i just set wrong values...
Post Reply