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...
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 updated the function. It's called now
SetChipVolume(volume.u, chip = 0)
The volume is a value between 0 and $400 where $400 means 4 times multiplication.
That sounds like enough for me as it's already causing distortion with a 4 times multiplication.
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 value need to get from extra, when it have?
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:this value need to get from extra, when it have?
Yes, the VGM v1.70 Extra Header can have both information about the clock setting of a chip and about the volume of a 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 »

it have one small problem :) you say command set a multiple, but:
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).
it can be multiple, and i understand how it work - multiple is multiple, but what does it mean absolute? it means maximum volume for volume 0 in a table? chip have 15 values... which one of them is this "absolute"? it is black hole :) no examples, no clear explanation of ValleyBell... i set any values for VGM and play in winamp - 0 to $FF - silence, $100 to $FFFF - i think same volume. no louder, no lower...

since 224 string it starts.

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
  
  #ProgressLoad
  #ProgressPlay
  
  #Volume1
  #Volume2
  
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

Global PlayPos
Global Arrayind

;{
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 Mute()
  
  Write(%10011111)
  Write(%10111111)
  Write(%11011111)
  Write(%11111111)
  
  Write(%10011111, 1)
  Write(%10111111, 1)
  Write(%11011111, 1)
  Write(%11111111, 1)
  
EndProcedure

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

  For i = start To Arrayind

    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
    
    SetGadgetState(#ProgressPlay, i)
    
    If PlayPos
      i = PlayPos
      PlayPos = 0
    EndIf    
    
  Next
  
  Debug "end of playing"
  
  ;silence when stop
  Mute()
  
  TormozFlag = 1
  
EndProcedure

Procedure ParsePlay(*FileMem)
  
  memsize = MemorySize(*FileMem) 
  SetGadgetAttribute(#ProgressLoad, #PB_ProgressBar_Maximum, memsize)

  ;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)

  ver = Val(ver$)  
  If ver < 151
    ot = *FileMem + 64      ; 64 - it is vgm header. no need it yet
  Else
    If ver < 171
      ot = *FileMem + 192   ; 192 header from 1.51 to 1.70
    Else
      ot = *FileMem + 256   ; 256 header from 1.71
    EndIf

    If ver > 159
      Debug "Volume modifier " + Str(PeekW(*FileMem + $7C))
    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
          
          If shift > 4 ; it means need to check volume            
            volshift = PeekL(*FileMem+$C8)            
            If volshift
              volchipcount = PeekB(*FileMem+$C8+volshift)
              If volchipcount
                readfrom = *FileMem+$C8+volshift + 1
                For i = 1 To volchipcount
                  ;If bit 7 is set, it's the volume for a paired chip. (e.g. the AY-part of 
                  ;the YM2203)
                  checkvalue = PeekB(readfrom)
                  If TestBit(checkvalue, NumToBit(7))
                    ;it means for both?
                  EndIf
                  
                  If GetBits(checkvalue, 0, 6) = 0 ; id of SN                  
                    ;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).
                    checkvalue = PeekU(readfrom + 2)
                    volvolue.u   = GetBits(checkvalue, 0, 14)
                    If TestBit(checkvalue, NumToBit(15))
                      ;multiplied
                      volvolue = ((volvolue & $7FFF) / $100)
                    EndIf                  
                  
                    ;If bit 0 is set, it's the volume for the second chip.
                    checkvalue = PeekB(readfrom + 1)
                    If TestBit(checkvalue, NumToBit(0))
                      ;or this place deside for both or only for second? 
                      ;if it is - what for was first "paired" check?
                      ;probably that paires is non PSG and can be ignored?
                      SetChipVolume(volvolue, 1)
                    Else
                      SetChipVolume(volvolue)
                      SetChipVolume(volvolue, 1)
                    EndIf
                    Debug volvolue
                  
                    Break
                  EndIf

                  readfrom + 4
                Next                
              EndIf              
            EndIf            
          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  
  
  progload = *FileMem
  fulllength = 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)
        fulllength + VGMARR(Arrayind)\pause
        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)
        fulllength + VGMARR(Arrayind)\pause
        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
        If Number > $80
          VGMARR(Arrayind)\type = 3
          VGMARR(Arrayind)\pause = (Number - $80)
          fulllength + 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
        fulllength + VGMARR(Arrayind)\pause
        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
        fulllength + VGMARR(Arrayind)\pause
        Arrayind + 1
        
      Case $66 ; end of sound
        Debug "end"        
        Break 
        
      Default 
        ;Debug "unknown command " + Hex(Number)
        
        
    EndSelect
    
    If TormozFlag
      Break
    EndIf
    
    If progload < i
      SetGadgetState(#ProgressLoad, i-*FileMem)      
      progload + 1000
    EndIf

  Next
  
  If TormozFlag = 0
    SetGadgetState(#ProgressLoad, memsize) ; fill 100%
    SetGadgetAttribute(#ProgressPlay, #PB_ProgressBar_Maximum, Arrayind)
    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, 240, 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")
  
  ProgressBarGadget(#ProgressLoad, 10, 42, 120, 2, 0, 100)
  
  ProgrX      =  10
  ProgrY      =  44
  ProgrWidth  = 120
  ProgrHeight =  10
  ProgressBarGadget(#ProgressPlay, ProgrX, ProgrY, ProgrWidth, ProgrHeight, 0, 100)
  
  
  TextGadget(#Text, 10, 60, 170, 50, "WARNING! it plays only SN76489. YM2612 is ignored. any VGM with other chips can crash programm.")
  
  StringGadget(#From, 10, 120, 65, 20, "0");"550")
  GadgetToolTip(#From, "starts from...")
  
  ButtonGadget(#Play, 80, 120, 50, 20, "play")
  ButtonGadget(#Stop, 135, 120, 50, 20, "stop")
  
  FrameGadget(#Fr01, 1, 158, 120, 97, "1 chip")
  FrameGadget(#Fr02, 120, 158, 120, 97, "2 chip")
  
  
  CheckBoxGadget(#Ch1L, 9, 170, 70, 20, "L   1Ch   R")
  CheckBoxGadget(#Ch2L, 9, 190, 70, 20, "L   2Ch   R")
  CheckBoxGadget(#Ch3L, 9, 210, 70, 20, "L   3Ch   R")
  CheckBoxGadget(#Ch4L, 9, 230, 70, 20, "L   4Ch   R")
  
  CheckBoxGadget(#Ch1R, 79, 170, 16, 20, "")
  CheckBoxGadget(#Ch2R, 79, 190, 16, 20, "")
  CheckBoxGadget(#Ch3R, 79, 210, 16, 20, "")
  CheckBoxGadget(#Ch4R, 79, 230, 16, 20, "")
  
  CheckBoxGadget(#Ch1L2, 128, 170, 70, 20, "L   1Ch   R")
  CheckBoxGadget(#Ch2L2, 128, 190, 70, 20, "L   2Ch   R")
  CheckBoxGadget(#Ch3L2, 128, 210, 70, 20, "L   3Ch   R")
  CheckBoxGadget(#Ch4L2, 128, 230, 70, 20, "L   4Ch   R")
  
  CheckBoxGadget(#Ch1R2, 198, 170, 16, 20, "")
  CheckBoxGadget(#Ch2R2, 198, 190, 16, 20, "")
  CheckBoxGadget(#Ch3R2, 198, 210, 16, 20, "")
  CheckBoxGadget(#Ch4R2, 198, 230, 16, 20, "")
  
  TrackBarGadget(#Volume1, 95, 166, 20, 85, 0, 10, #PB_TrackBar_Vertical)
  SetGadgetState(#Volume1, 5)
  
  TrackBarGadget(#Volume2, 213, 166, 20, 85, 0, 10, #PB_TrackBar_Vertical)
  SetGadgetState(#Volume2, 5)
  
 
  
  
  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$
              Mute()
              For i = #Ch1R To #Ch4L2
                SetGadgetState(i, 1)
              Next
              GGStereoWrite(255)
              GGStereoWrite(255, 1)              
              ;SetGadgetState(#Ch2R2, 1)              
              ;SetGadgetState(#Ch2L2, 1)
              ;GGStereoWrite(0)
              ;GGStereoWrite(34, 1)
              
              TormozFlag = 1
              If IsThread(PlThr)
                KillThread(PlThr)
                If IsThread(PlThr)
                  WaitThread(PlThr)
                EndIf
              EndIf
              SetGadgetText(#PathString, File$)
              SetGadgetState(#ProgressLoad, 0)
              
              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 unpsz
                  
                    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   
                  
                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
            
          Case #Volume1
            VolumeL.u
            Select GetGadgetState(#Volume1)
              Case 10
                VolumeL = $400
              Case 9
                VolumeL = $300
              Case 8
                VolumeL = $200
              Case 7
                VolumeL = $150
              Case 6
                VolumeL = $120
              Case 5
                VolumeL = $100
              Case 4
                VolumeL = $80
              Case 3
                VolumeL = $60
              Case 2
                VolumeL = $40
              Case 1
                VolumeL = $20             
              Case 0
                VolumeL = $0
            EndSelect
            SetChipVolume(VolumeL)
            
          Case #Volume2
            VolumeL.u
            Select GetGadgetState(#Volume2)
              Case 10
                VolumeL = $400
              Case 9
                VolumeL = $300
              Case 8
                VolumeL = $200
              Case 7
                VolumeL = $150
              Case 6
                VolumeL = $120
              Case 5
                VolumeL = $100
              Case 4
                VolumeL = $80
              Case 3
                VolumeL = $60
              Case 2
                VolumeL = $40
              Case 1
                VolumeL = $20             
              Case 0
                VolumeL = $0
            EndSelect
            SetChipVolume(VolumeL, 1)
            
        EndSelect
        
      Case 512 ;{ mousemove
        xmouse = WindowMouseX(0)
        ymouse = WindowMouseY(0)  
        ;}
        
      Case 513 ;{ leftclick
         If IsThread(PlThr) And (xmouse > ProgrX And xmouse < ProgrX + ProgrWidth) And (ymouse > ProgrY And ymouse < ProgrY + ProgrHeight)
           Mute()
           getoneproc.f   = GetGadgetAttribute(#ProgressPlay, #PB_ProgressBar_Maximum) / 100
           getwidthproc.f = ProgrWidth / 100           
           getabsvalue    = xmouse - ProgrX
           PlayPosF.f     = getabsvalue / getwidthproc
           PlayPosF * getoneproc
           PlayPos = PlayPosF
         EndIf
       ;}     

      Case #PB_Event_CloseWindow
        TormozFlag = 1
        Delay(10)
        If IsThread(PlThr)
          KillThread(PlThr)
          If IsThread(PlThr)
            WaitThread(PlThr)
          EndIf
        EndIf
        Quit = 1
         
    EndSelect

  Until Quit = 1
  
EndIf

End
Last edited by SeregaZ on Wed Sep 28, 2016 7:00 am, 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:it have one small problem :) you say command set a multiple, but:
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).
it can be multiple, and i understand how it work - multiple is multiple, but what does it mean absolute? it means maximum volume for volume 0 in a table? chip have 15 values... which one of them is this "absolute"? it is black hole :) no examples, no clear explanation of ValleyBell... i set any values for VGM and play in winamp - 0 to $FF - silence, $100 to $FFFF - i think same volume. no louder, no lower...
As far as I understand for the SN76489 the ratio is always 2:1 .
So a relative volume of $100 is the same as an absolute volume of $80.
A relative volume of $200 is the same as an absolute volume of $100.
In other words, with the same value for absolute and relative, absolute should be 3 dB louder.

I found another volume related thing :?
v1.60 and above have Volume Modifier (header offset $7c).
Don't know if it's important or not.
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 am forgot set break and id 0 checking :)

anyway - where to get examples for check how correct this values is reads and apply? that test vgm - i make, and i am not sure how correct i made it. i think if some values make silence - it is right place, but why volume have only two cases - sound or silence :) i didnt hear difference by set any values.

and i am start hate VGM :) maybe we are will kick out extra volume header and will use only this volume modifier? btw this train song says this modifier is 24.

so volume is:
note volume 15 cases
extra header setting
volume modifier

too much for volume :)

note volume 15 cases - individual note
extra header setting - for both, or just for second.
volume modifier - for both chips?
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:and i am start hate VGM :) maybe we are will kick out extra volume header and will use only this volume modifier?
I think for now it might be best to ignore both that volume modifier and the difference between absolute and relative volume setting.
The volume modifier is not related to a specific chip but to the entire player in general.
The difference between absolute and relative volume varies per chip model. :?

I posted an update of the module
I added SetNoisePattern which allows to set the noise pattern that is used.
Clock is renamed now to SetClock for consistency with other procedure names.
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 »

2016-09-28 12:41:44 ValleyBell 0x7C is a global setting used to makes quiet VGMs louder.
2016-09-28 12:57:23 ValleyBell (tl;dr: It is "Replay Gain" for VGMs.)
I think for now it might be best to ignore both that volume modifier and the difference between absolute and relative volume setting.
i agree :) frequency for chip will be enough. other params can go to hell :twisted:
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 a small update.
The bit from the clock frequency which indicates a second chip should only be looked at when the clock for the first chip is set.
With the previous version, setting the clock of the second chip without the bit set put it back to one chip again.

v 1.43 fixes a noise frequency problem.
Windows (x64)
Raspberry Pi OS (Arm64)
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 »

Here's a rough attempt at a playback which works on Windows and MacOS also (my main OS).
The Delay() method wan't accurate enough for that on MacOS.

Here's v 1.43 of the include file
http://www.purebasic.fr/english/viewtop ... 43#p494943

Code: Select all

XIncludeFile "SN76489.pbi"

CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Windows
    Macro ztype:l:EndMacro  
    #ZLIB_Filename = "zlib.lib"
  CompilerCase #PB_OS_Linux
    Macro ztype:i:EndMacro  
    #ZLIB_Filename = #PB_Compiler_Home + "purelibraries/linux/libraries/zlib.a"
  CompilerDefault
    Macro ztype:i:EndMacro  
    #ZLIB_Filename = "-lz"
CompilerEndSelect

Structure zstream_ Align #PB_Structure_AlignC
  *next_in
  avail_in.ztype
  total_in.ztype
  *next_out
  avail_out.ztype
  total_out.ztype
  *msg
  *state
  *zalloc
  *zfree
  *opaque
  data_type.ztype
  adler.ztype
  reserved.ztype
EndStructure

ImportC #ZLIB_Filename
  inflate(*strm, flush)
  inflateEnd(*strm)
  inflateInit2_(*strm, windowBits, *version, stream_size)
  zlibVersion()
EndImport

;-Catch and Load VGM files

Procedure.i CatchVGM(*VGM, Size = -1)
  Protected strm.zstream_, *m, *l.Long = *vgm
  Protected.i done, vgmsize
  If *l
    Size & $3fffffff
    If *l\l = $206d6756
      *l + 4 : vgmsize = *l\l + 4
      *l = AllocateMemory(vgmsize)
      If *l And vgmsize <= Size
        CopyMemory(*vgm, *l, vgmsize)
        done = #True
      EndIf
    ElseIf *l\l & $ffff = $8b1f
      *l = AllocateMemory(65536)
      If *l
        strm\next_in = *vgm
        strm\avail_in = Size
        strm\next_out = *l
        strm\avail_out = 65536
        If inflateInit2_(@strm, 31, zlibVersion(), SizeOf(zstream_)) = 0
          inflate(@strm, 0)
          If *l\l = $206d6756
            *l + 4 : vgmsize = *l\l + 4 : *l - 4
            If vgmsize
              *m = ReAllocateMemory(*l, vgmsize)
              If *m
                *l = *m
                If strm\total_out < vgmsize
                  strm\avail_out = vgmsize - 65536
                  inflate(@strm, 4)
                EndIf
                If strm\total_out = vgmsize
                  done = #True
                EndIf
              EndIf
            EndIf
          EndIf
        EndIf
      EndIf
    EndIf
  EndIf
  If *l And Not done
    FreeMemory(*l) : *l = 0
  EndIf
  ProcedureReturn *l  
EndProcedure

Procedure.i LoadVGM(Filename.s)
  Protected.i file, size, *m, *vgm
  file = ReadFile(#PB_Any, Filename)
  If file
    size = Lof(file) : *m = AllocateMemory(size)
    If *m
      If ReadData(file, *m, size) = size
        *vgm = CatchVGM(*m)
      EndIf
      CloseFile(file)
      FreeMemory(*m)  
    EndIf
  EndIf
  ProcedureReturn *vgm
EndProcedure

Procedure FreeVGM(*VGM)
  If *VGM : FreeMemory(*VGM) : EndIf
EndProcedure



Structure VGMData
  l.l[0] : u.u[0] : a.a[0]
EndStructure

Structure VGMCommand
  cmd.a : u.u[0] : a.a[0]
EndStructure

DataSection
  SkipTable:
  ;      0 1 2 3 4 5 6 7 8 9 a b c d e f
  Data.a 1,1,1,2,3,3,0,1,1,0,3,3,4,4,5,5  ; cmd skip
  Data.a 5,5,6,11,2,5                     ; DAC skip
EndDataSection

Global *VGM, *CurrentPos, lOffset, EnableVGM, Wait

Procedure RenderVGM(*buf, len.l)
  Protected.l version, dOffset, lOffset, i, skip
  Protected *cmd.VGMCommand, cmd.a
  Protected.VGMData *v_, *v = *VGM, *skipTable = ?SkipTable
  If EnableVGM And *v And *v\l[0] = $206d6756
    
    ; >> header <<
    If *CurrentPos - *VGM < $40
      version = *v\l[2]                       ; VGM version
      lOffset = *v\l[7]                       ; loop offset
      If lOffset : lOffset + $1C : EndIf
      dOffset = *v\l[13] + $34                ; data offset
      If version < $150 Or dOffset < $40
        dOffset = $40
      EndIf
      SN76489::SetClock(*v\l[3])              ; SN76489 clock
      If version < $110
        SN76489::SetNoisePattern($100009)     ; SN76489 noise
      Else
        SN76489::SetNoisePattern(*v\l[10])
      EndIf
      If dOffset >= $C0                       ;  extra header
        *v = *v\l[47] + $BC
        If *v >= $C0 And dOffset > *v
          *v + *VGM
          If *v\l[0] >= 8
            If *v\l[1]
              *v_ = *v + *v\l[1] + 4
              i = *v_\a[0] : *v_ + 1
              While i : i - 1
                If *v_\a[0] = 0; SN76489 ?
                  *v_ + 1
                  SN76489::SetClock(*v_\l[0], 1)
                  *v_ + 4
                Else
                  *v_ + 5
                EndIf
              Wend
            EndIf
          EndIf
          If *v\l[0] >= 12
            If *v\l[2]
              *v_ = *v + *v\l[2] + 8
              i = *v_\a[0] : *v_ + 1
              While i : i - 1
                If *v_\a[0] = 0; SN76489 ?
                  SN76489::SetChipVolume(*v_\u[1], *v_\a[1] & 1)
                EndIf
                *v_ + 4
              Wend
            EndIf
          EndIf
        EndIf  
      EndIf
      *CurrentPos = *VGM + dOffset
    EndIf
    
    ; >> commands <<
    If *CurrentPos - *VGM >= $40
      
      If Wait
        If Wait >= len
          SN76489::Render(*buf, len)
          Wait - len
          ProcedureReturn
        Else
          SN76489::Render(*buf, Wait)
          *buf + Wait << 2 : len - Wait
          Wait = 0
        EndIf
      EndIf  
      
      *cmd = *CurrentPos
      While len
        wait = 0
        While wait = 0
          cmd = *cmd\cmd
          Select cmd
            Case $50, $30   : SN76489::Write(*cmd\a[0], cmd >> 5 & 1) : *cmd + 2
            Case $4F, $3F   : SN76489::GGStereoWrite(*cmd\a[0], cmd >> 5 & 1) : *cmd + 2
            Case $70 To $7F : wait = cmd & $f + 1 : *cmd + 1
            Case $61        : wait = *cmd\u[0] : *cmd + 3
            Case $62        : wait = 735 : *cmd + 1
            Case $63        : wait = 882 : *cmd + 1
            Case $67        : *v_ = *cmd + 3 : *cmd + 7 + (*v_\l[0] & $7fffffff)
            Case $68        : *cmd + 12
            Case $90 To $95 : *cmd + *skipTable\a[(cmd & $f) | $10]
            Case $66:
              ; normal end of data => loop
              Debug "end"
              If lOffset
                *cmd = *VGM + lOffset
              Else
                EnableVGM = #False
                wait = 10000
              EndIf          
            Default:
              skip = *skipTable\a[cmd >> 4] : *cmd + skip
              If skip = 0
                ; unknown command, quit
                EnableVGM = #False
              EndIf
          EndSelect
        Wend
        
        If Wait > len
          SN76489::Render(*buf, len) : *buf + len << 2
          Wait - len
          Break
        EndIf
        SN76489::Render(*buf, wait) : *buf + wait << 2
        len - wait
        
      Wend
      *CurrentPos = *cmd
      
    EndIf
    
  Else
    FillMemory(*buf, len << 2)
  EndIf
EndProcedure



SN76489::SN76489()

CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
  
  Import "-stdlib=libc++ -mmacosx-version-min=10.7" : EndImport
  
  DataSection
    dOut:
    Data.l $61756f75,$64656620,$6170706c,0,0
    PCM16Bit:
    Data.l 0,$40E58880,$6C70636D,12,4,1,4,2,16,0
  EndDataSection
  
  Global PSG_Unit.i
  
  Procedure PSG_Init(*CallbackFunction, BufferSize.l = 2048)
    Protected Dim CallbackStruct.i(1) : CallbackStruct(0) = *CallbackFunction
    AudioComponentInstanceNew_(AudioComponentFindNext_(#Null, ?dOut), @PSG_Unit)
    AudioUnitSetProperty_(PSG_Unit, 23, 1, 0, @CallbackStruct(), SizeOf(Integer) << 1)
    AudioUnitSetProperty_(PSG_Unit, 8, 1, 0, ?PCM16Bit, 40)
    AudioUnitSetProperty_(PSG_Unit, $6673697a, 1, 0, @BufferSize, 4)
    AudioUnitInitialize_(PSG_Unit)
  EndProcedure
  
  Procedure PSG_Start()
    ProcedureReturn Bool(AudioOutputUnitStart_(PSG_Unit) = 0)
  EndProcedure
  
  Procedure PSG_Stop()
    AudioOutputUnitStop_(PSG_Unit)
  EndProcedure
  
  Procedure PSG_Terminate()
    If PSG_Unit
      AudioOutputUnitStop_(PSG_Unit)
      AudioUnitUninitialize_(PSG_Unit)
      AudioComponentInstanceDispose_(PSG_Unit)
      PSG_Unit = 0
    EndIf
  EndProcedure
  
  ProcedureC Callback(*inRefcon, *ioActionFlags, *inTimeStamp, inBusNumber.l, inNumberFrames.l, *ioData)
    RenderVGM(PeekI(*ioData + SizeOf(Integer) + 8), inNumberFrames)
    ProcedureReturn 0
  EndProcedure
  
  PSG_Init(@Callback())
  PSG_Start()
  
CompilerElse
  
  #PSG_BUFFERS = 4
  Global Dim wavebuf.w(#PSG_BUFFERS - 1, 2047)
  Global Dim wavehdr.WAVEHDR(#PSG_BUFFERS - 1)
  Global.i PSG_Unit, WMME_Playing
  
  DataSection
    PCM16BitSigned:
    Data.w 1,2,$AC44,0,$B110,2,4,16,0
  EndDataSection  
  
  Procedure WMME_Callback(hwo, uMsg, dwInstance, *wavehdr.WAVEHDR, dwParam2)
    If WMME_Playing And uMSG = #WOM_DONE
      RenderVGM(*wavehdr\lpData, 1024)
      waveOutWrite_(hwo, *wavehdr, SizeOf(WAVEHDR))
    EndIf
  EndProcedure
  
  Procedure PSG_Terminate()
    Protected i.i
    If PSG_Unit
      WMME_Playing = #False
      waveOutReset_(PSG_Unit)
      While i < #PSG_BUFFERS     
        waveOutUnprepareHeader_(PSG_Unit, @wavehdr(i), SizeOf(WAVEHDR))
        i + 1
      Wend        
      waveOutClose_(PSG_Unit)
      PSG_Unit = 0
    EndIf
  EndProcedure  
  
  Procedure PSG_Init()
    If waveOutOpen_(@PSG_Unit, #WAVE_MAPPER, ?PCM16BitSigned, @WMME_Callback(), 0, #CALLBACK_FUNCTION)
      MessageRequester("Error", "Unable to initialize PSG")
    Else
      While i < #PSG_BUFFERS
        wavehdr(i)\lpData = @wavebuf(i, 0)
        wavehdr(i)\dwBufferLength = 4096
        waveOutPrepareHeader_(PSG_Unit, @wavehdr(i), SizeOf(WAVEHDR))
        waveOutWrite_(PSG_Unit, @wavehdr(i), SizeOf(WAVEHDR))
        i + 1
      Wend
      WMME_Playing = #True
    EndIf
  EndProcedure
  
  PSG_Init()
  
CompilerEndIf

FileName.s = OpenFileRequester("Choose VGM file", "", "", 0)
*VGM = LoadVGM(FileName)
EnableVGM = #True

MessageRequester("VGM", "Playing")

PSG_Terminate()
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 see you are not read vgm to array, but directly play?
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 see you are not read vgm to array, but directly play?
Yes, that's true.
Is there any reason you prefer to use an array ?
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 »

for PSG probably not. for YM it need for DAC samples playing. problem is - that ValleyBell's dll play samples by external command. it need frequency, size and adress of sample.

by theory samples is play by chip - by using $2A registers, but it didnt work, so ValleyBell make that external command. system must be some kind of your PSG samples play (btw your not plays too :))) some scratch and noise hear).

then come problem with this frequency and size - VGM no have frequency settings for samples and size. that is why i read into array, then make puzzles for samples, only then play. even this case not very well helps. this opn.dll have some problem with save cpu - it is shutdown of playing, if samples have silence. for example: sample length 2 sec, at begin it have some sound, at middle some silence, at the end some play - this dll play first part, then shutdown. he think samples is end if pause more than 0.5 sec.

second problem: samples starts VGM by setting address of sample in wav bank - some kind of setting pointer - i catch this moment and start collect sample byte per byte. and it work and frequency count correct and sample size, BUT! if track have two samples one and second and they lays at wav bank near each other one and second - setting of adress of second samples can be not happen. pointer just continue move far. and beetween of this samples can be large pause - this moment is broke all my super system :))) that is why i start this topic - find help to solve this samples problem.

i think check all track, get all adresses, cut wav bank, then check oversize of sample. if this oversize is happen - it means at this moment starts second sample. it will work, if track will have this second marker of adress, but if is not - deam again :)
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 »

and about your delays - we talk about it here:
http://www.purebasic.fr/english/viewtop ... 13&t=62919

maybe some one can help with pauses for your system?
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:for YM it need for DAC samples playing. problem is - that ValleyBell's dll play samples by external command. it need frequency, size and adress of sample.
Sounds difficult to get it right. A FM chip is already more complicated. What I like about the PSG is the relative simplicity of it.
SeregaZ wrote:and about your delays - we talk about it here:
I thought about it some more. It could also be caused by different ringbuffer implementations on different OS.
Usually there are multiple sample buffers which are queued. Depending on when this is done, timing can be a bit off if you don't make changes at sample accurate timing.
Windows (x64)
Raspberry Pi OS (Arm64)
Post Reply