[solved] wave-files (merge)

Just starting out? Need help? Post your questions and find answers here.
ZX80
Enthusiast
Enthusiast
Posts: 330
Joined: Mon Dec 12, 2016 1:37 pm

[solved] wave-files (merge)

Post by ZX80 »

Hello.
I need to merge several sound files into one. Assume that all files have the same format. I read this topic and want to use code written by infratec. Here is a non-working example(very dirty), but it shows what I want to do (based on code by infratec):

Code: Select all

Structure RIFFStructure
  Riff.a[4]
  Length.l
  Wave.a[4]
EndStructure

Structure chunkStructure
  Signature.a[4]
  Length.l
EndStructure

Structure fmtStructure
  fmt.a[4]
  Length.l
  Format.u
  Channels.u
  SampleRate.l
  BytesPerSecond.l
  BlockAlign.u
  BitsPerSample.u
EndStructure

Structure dataStructure
  dataChunk.a[4]
  Length.l
 
  dataBytes.a[0]
EndStructure


Structure properties
  begin_position.q
  size.q
EndStructure


#HeaderSize = 44 ; standard size for wav-file header
Global OutputFile.s = "c:\base.wav" ; result file
Global size_of_data.q
Global NewList DATABase.properties()


Procedure.i LoadSoundWav(Filename$)
  Protected.i File, FileSize
  Protected Chunk$
  Protected *ReadBuffer, *WAVBuffer
  Protected *RiffPtr.RIFFStructure
  Protected *chunkPtr.chunkStructure
  Protected *fmtPtr.fmtStructure
  Protected *dataPtr.dataStructure
 
 
  File = ReadFile(#PB_Any, Filename$)
  If File
    
    FileSize = Lof(File)
    *ReadBuffer = AllocateMemory(FileSize)
    If *ReadBuffer

      *WAVBuffer = AllocateMemory(FileSize)
      If *WAVBuffer
       
        If ReadData(File, *ReadBuffer, FileSize) = FileSize
         
          *RiffPtr = *ReadBuffer
          If PeekS(@*RiffPtr\Riff, 4, #PB_Ascii) = "RIFF" And *RiffPtr\Length = FileSize - 8 And PeekS(@*RiffPtr\Wave, 4, #PB_Ascii) = "WAVE"
            ;Debug "Header Ok"
           
            CopyMemory(*RiffPtr, *WAVBuffer, SizeOf(RIFFStructure))
           
           
            *chunkPtr = *ReadBuffer + SizeOf(RIFFStructure)
           
            Repeat
              ; check for data chunk
           
              Chunk$ = PeekS(@*chunkPtr\Signature, 4, #PB_Ascii)
           
              ;Debug "Chunk: " + Chunk$
             
              Select Chunk$
                Case "fmt "
                  *fmtPtr = *chunkPtr
                  CopyMemory(*fmtPtr, *WAVBuffer + SizeOf(RIFFStructure), *fmtPtr\Length + 8)
                  
                Case "data"
                  *dataPtr = *chunkPtr
                  CopyMemory(*dataPtr, *WAVBuffer + SizeOf(RIFFStructure) + *fmtPtr\Length + 8, *dataPtr\Length + 8)
                  
              EndSelect
             
              *chunkPtr + 8 + *chunkPtr\Length
             
            Until *chunkPtr >= *ReadBuffer + *RiffPtr\Length
           
           
            *RiffPtr = *WAVBuffer
            PokeL(@*RiffPtr\Length, SizeOf(RIFFStructure) - 8 + *fmtPtr\Length + 8 + *dataPtr\Length + 8)
            
            
            resultfile_size.q = FileSize(OutputFile)
            If resultfile_size =-1
              CreateFile(99, OutputFile)
              WriteData(99, *WAVBuffer, #HeaderSize) ; write original header
;              CloseFile(99)
            Else
              OpenFile(99, OutputFile)
              FileSeek(99, 40)
              WriteData(99, @size_of_data, 4) ; change size of data (position: 4 Bytes after 'data')
              FileSeek(99, Lof(99)) ; the same as #PB_File_Append
            EndIf

            size_of_data + PeekL(*WAVBuffer+#HeaderSize-4) ; how to sum bytes here ???
            WriteData(99, *WAVBuffer + #HeaderSize, *RiffPtr\Length + 8 - #HeaderSize)
            CloseFile(99)
            AddElement(DATABase())
            If resultfile_size
              DATABase()\begin_position = resultfile_size
            Else
              DATABase()\begin_position = #HeaderSize
            EndIf
            DATABase()\size = *RiffPtr\Length + 8 - #HeaderSize
           
          EndIf
         
        EndIf
       
        FreeMemory(*WAVBuffer)
      EndIf
     
      FreeMemory(*ReadBuffer)
    EndIf
   
  EndIf
EndProcedure




For i=1 To 3
  LoadSoundWav("c:\"+Str(i)+".wav")
Next i


; now I want to get a second sound from the base
SelectElement(DATABase(), 1)
OpenFile(99, OutputFile)

*Header = AllocateMemory(#HeaderSize)
ReadData(99, *Header, #HeaderSize)

FileSeek(99, DATABase()\begin_position)

_DATA = DATABase()\size
*ReadBuffer = AllocateMemory(_DATA)
ReadData(99, *ReadBuffer, _DATA)
CloseFile(99)


; create a new file
OpenFile(99, "c:\taste.wav")
WriteData(99, *Header, #HeaderSize-4)
WriteData(99, @_DATA, 4) ; ??? hex()
WriteData(99, *ReadBuffer, _DATA)

CloseFile(99)
FreeMemory(*Header)
FreeMemory(*ReadBuffer)

Last edited by ZX80 on Thu Aug 16, 2018 7:22 pm, edited 1 time in total.
User avatar
CELTIC88
Enthusiast
Enthusiast
Posts: 154
Joined: Thu Sep 17, 2015 3:39 pm

Re: wave-files (merge)

Post by CELTIC88 »

hello :D
tested with this file :arrow: http://www.mediafire.com/file/kskn01k96 ... 1.wav/file

Code: Select all

Structure wavfileheader 
  ;// RIFF Header
  riff_header.l; // Contains "RIFF"
  wav_size.l   ; // Size of the wav portion of the file, which follows the first 8 bytes. File size - 8
  wave_header.l; // Contains "WAVE"
  
  ;// Format Header
  fmt_header.l; // Contains "fmt " (includes trailing space)
  fmt_chunk_size.l; // Should be 16 for PCM
  audio_format.w  ; // Should be 1 for PCM. 3 for IEEE Float
  num_channels.w  ;
  sample_rate.l   ;
  byte_rate.l     ; // Number of bytes per second. sample_rate * num_channels * Bytes Per Sample
  sample_alignment.w; // num_channels * Bytes Per Sample
  bit_depth.w       ; // Number of bits per sample
  
  ;// Data
  data_header.l; // Contains "data"
  data_bytes.l ; // Number of bytes in data. Number of samples * num_channels * sample byte size
               ;// uint8_t bytes[]; // Remainder of wave file is bytes
EndStructure

swavfileheader.wavfileheader
swavfileheader2.wavfileheader
ReadFile(0,"1.wav",#PB_File_SharedRead)
ReadData(0,swavfileheader,SizeOf(wavfileheader))
ReadFile(1,"1.wav",#PB_File_SharedRead)
ReadData(1,swavfileheader2,SizeOf(wavfileheader))

siz = swavfileheader\data_bytes
p = AllocateMemory(siz)
ReadData(0,p,siz)

CreateFile(2, "3.wav")
swavfileheader\wav_size + swavfileheader2\data_bytes
swavfileheader\data_bytes + swavfileheader2\data_bytes
WriteData(2,swavfileheader,SizeOf(wavfileheader))
WriteData(2,p,siz)
p = AllocateMemory(swavfileheader2\data_bytes)
ReadData(1,p,swavfileheader2\data_bytes)
WriteData(2,p,swavfileheader2\data_bytes)
CloseFile(2)
interested in Cybersecurity..
ZX80
Enthusiast
Enthusiast
Posts: 330
Joined: Mon Dec 12, 2016 1:37 pm

Re: wave-files (merge)

Post by ZX80 »

CELTIC88, thanks a lot!!!
It does. Tested with 16-bit wav-files. With 24-bit sample - not work (don't need, only for info). Comment in structure //Should be 16 for PCM.
If I need merge much than two files, then for each of files need declare new copy of wavfileheader-structure?
For example, I have directory contains about 50-files. Enumerate files - not a problem, but I want create one big file (database of sounds). This file must have only one wave-header. But... I also want to create list-file(during create database-file) with follow description(bias from beginning of database-file and size of data/raw). In the end, I need to create a sentence from some words (using data from list-file). Please, comment my question about new copy of wavfileheader-structure for each of files.
Until you answered I looked ffmpeg for this task.
Your version is better than additional dlls of ffmpeg.
Thanks again.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: wave-files (merge)

Post by wilbert »

@CELTIC88
It's not safe to assume the "data" chunk fill immediately follow the "fmt " chunk.
A LIST INFO chunk could be in between with information about title, artist etc.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
CELTIC88
Enthusiast
Enthusiast
Posts: 154
Joined: Thu Sep 17, 2015 3:39 pm

Re: wave-files (merge)

Post by CELTIC88 »

hi Dr @wilbert :D

i know :P , you can skip the "LIST INFO" with finding magic number "data" :

Code: Select all

While swavfileheader\data_header <> $61746164 ; "data"
  FileSeek(0,swavfileheader\data_bytes,#PB_Relative)
  If ReadData(0,@swavfileheader\data_header,8) = 0 :End 1 : EndIf
Wend
thank you for your comment


hi @ZX80
..With 24-bit sample - not work (don't need, only for info)
not tested :p,but should be work!
..then for each of files need declare new copy of wavfileheader-structure?
No!. look my example ,after reading the header my file is pointed to wav data

thank .
interested in Cybersecurity..
ZX80
Enthusiast
Enthusiast
Posts: 330
Joined: Mon Dec 12, 2016 1:37 pm

Re: wave-files (merge)

Post by ZX80 »

Hi, CELTIC88
Very glad to hear you again. Sorry for my stupid previous question and post without code. Today I was busy a whole time. Without access to PC and Internet. But today I have a fresh brain and eyes. And I have some idea how to do this. I'll try to write this, and you'll check, okay? I can not say exactly what it will be today. Please wait.
tatne
New User
New User
Posts: 1
Joined: Tue Aug 14, 2018 12:31 am

Re: wave-files (merge)

Post by tatne »

ZX80 wrote:Hi, CELTIC88
Very glad to hear you again. Sorry for my stupid previous question and post without code. Today I was busy a whole time. Without access to PC and Internet. But today I have a fresh brain and eyes. Many people have this issue, where they need a fresh day to start over. And I have seen some information here which shows how to do it and how to deal with all the problems. So, I think it should be doable. I have some idea how to do this. I'll try to write this, and you'll check, okay? I can not say exactly what it will be today. Please wait.
hello,

are you going to post it today?
Last edited by tatne on Fri Aug 24, 2018 7:28 pm, edited 1 time in total.
ZX80
Enthusiast
Enthusiast
Posts: 330
Joined: Mon Dec 12, 2016 1:37 pm

Re: wave-files (merge)

Post by ZX80 »

are you going to post it today?
Hi.
Yes, but ... something wrong :(
Please, check it.
create_db.pb:

Code: Select all

Structure wavfileheader 
  ;// RIFF Header
  riff_header.l; // Contains "RIFF"
  wav_size.l   ; // Size of the wav portion of the file, which follows the first 8 bytes. File size - 8
  wave_header.l; // Contains "WAVE"
  
  ;// Format Header
  fmt_header.l; // Contains "fmt " (includes trailing space)
  fmt_chunk_size.l; // Should be 16 for PCM
  audio_format.w  ; // Should be 1 for PCM. 3 for IEEE Float
  num_channels.w  ;
  sample_rate.l   ;
  byte_rate.l     ; // Number of bytes per second. sample_rate * num_channels * Bytes Per Sample
  sample_alignment.w; // num_channels * Bytes Per Sample
  bit_depth.w       ; // Number of bits per sample
  
  ;// Data
  data_header.l; // Contains "data"
  data_bytes.l ; // Number of bytes in data. Number of samples * num_channels * sample byte size
               ;// uint8_t bytes[]; // Remainder of wave file is bytes
EndStructure


Structure Properties
  _bias.i
  _size.i
EndStructure


Global NewList MyFiles$()

Procedure Direct(Directory.s, maska.s)
If Right(Directory.s,1)<>"\":Directory.s + "\":EndIf 
   z=ExamineDirectory(#PB_Any, Directory.s, "*.*")  
  If z 
    While NextDirectoryEntry(z) 
     EntryName.s=DirectoryEntryName(z) 

      If EntryName = "." Or EntryName = ".." 
       Continue 
      EndIf 

      Type=DirectoryEntryType(z)
      If Type = #PB_DirectoryEntry_Directory
        Direct(Directory.s+EntryName, maska.s)  
      ElseIf Type = #PB_DirectoryEntry_File
     
      FileName.s= Directory.s+EntryName
          Ext$ = LCase(Right(FileName.s,4)) 
           If  Ext$ = maska
             AddElement(MyFiles$()) 
             MyFiles$()= FileName   
           EndIf
      EndIf 
    Wend 
    FinishDirectory(z) 
  EndIf 
EndProcedure

; Scan folder
InitialPath$ = "C:\"
Path$ = PathRequester("Please choose dir with wavs", InitialPath$)
If Path$ = ""
  MessageRequester("Information", "The requester was canceled.")
  End
EndIf

Direct(Path$,".wav")
If ListSize(MyFiles$())=0
  MessageRequester("Information", "wavs not found.")
  End
EndIf

*mem = AllocateMemory(10000000) ; MAX size for wav-file = 10 MBytes
If *mem = 0
  MessageRequester("Information", "memory cannot be allocated")
  End
EndIf

NewList SoundsDB.Properties()
bias = SizeOf(wavfileheader) ; pointer (start position)
headersize = bias
database.s = "SoundsDB.wav"
header.wavfileheader

; create blank
If CreateFile(1, database) = 0
  MessageRequester("Information", "can't create db-file")
  End
EndIf


; and now read content of wavs
ForEach MyFiles$()
  If ReadFile(0, MyFiles$(), #PB_File_SharedRead)
    If ReadData(0, header, headersize)
      bias + size
      size = header\data_bytes  ; size of raw-data
      ReadData(0, *mem, size)
      CloseFile(0)
      If ListSize(SoundsDB())=0
        WriteData(1, header, headersize)
      EndIf
      AddElement(SoundsDB())
      SoundsDB()\_bias = bias
      SoundsDB()\_size = size
      WriteData(1, *mem, size)
    EndIf
  EndIf
Next
FileSeek(1, 0)
SelectElement(SoundsDB(), 0)
header\data_bytes = bias + SoundsDB()\_size
WriteData(1, header, headersize)
CloseFile(1)
FreeMemory(*mem)
If ListSize(SoundsDB())=0
  MessageRequester("Information", "Something wrong :(")
  End
Else
  If CreateFile(1, "list.txt") = 0
    MessageRequester("Information", "can't create list-file")
    End
  Else
    ForEach SoundsDB()
      WriteStringN(1, Str(SoundsDB()\_bias) + "|" + Str(SoundsDB()\_size))
    Next
  EndIf
EndIf
and get_from_db.pb:

Code: Select all

Structure wavfileheader 
  ;// RIFF Header
  riff_header.l; // Contains "RIFF"
  wav_size.l   ; // Size of the wav portion of the file, which follows the first 8 bytes. File size - 8
  wave_header.l; // Contains "WAVE"
  
  ;// Format Header
  fmt_header.l; // Contains "fmt " (includes trailing space)
  fmt_chunk_size.l; // Should be 16 for PCM
  audio_format.w  ; // Should be 1 for PCM. 3 for IEEE Float
  num_channels.w  ;
  sample_rate.l   ;
  byte_rate.l     ; // Number of bytes per second. sample_rate * num_channels * Bytes Per Sample
  sample_alignment.w; // num_channels * Bytes Per Sample
  bit_depth.w       ; // Number of bits per sample
  
  ;// Data
  data_header.l; // Contains "data"
  data_bytes.l ; // Number of bytes in data. Number of samples * num_channels * sample byte size
               ;// uint8_t bytes[]; // Remainder of wave file is bytes
EndStructure

Structure Properties
  _bias.i
  _size.i
EndStructure

NewList SoundsDB.Properties()
database.s = "SoundsDB.wav"
headersize = SizeOf(wavfileheader)
header.wavfileheader

*buffer = AllocateMemory(10000000)
If *buffer = 0
  MessageRequester("Information", "memory cannot be allocated")
  End
EndIf

If FileSize(database)=-1
  MessageRequester("Information", "db not found")
  End
EndIf
If FileSize("list.txt") = -1
  MessageRequester("Information", "list-file not found")
  End
EndIf


If ReadFile(0, "list.txt")
  While Eof(0) = 0
    tmp$ = ReadString(0)
    If FindString(tmp$, "|")
      AddElement(SoundsDB())
      SoundsDB()\_bias = Val(StringField(tmp$, 1, "|"))
      SoundsDB()\_size = Val(StringField(tmp$, 2, "|"))
    EndIf
  Wend
  CloseFile(0)
Else
  MessageRequester("Information","Couldn't open the db-file!")
EndIf

;get sound1 + sound4
If CreateFile(1, "output.wav")=0
  MessageRequester("Information", "can't create output-file")
  End
EndIf
If ReadFile(0, database, #PB_File_SharedRead) = 0
  MessageRequester("Information", "can't read db-file")
  End
EndIf
If ReadData(0, header, headersize) = 0
  MessageRequester("Information", "readdata err")
  End
EndIf

; create wav-header with correct size of raw-data
SelectElement(SoundsDB(), 0)
size = SoundsDB()\_size
SelectElement(SoundsDB(), 3)
size + SoundsDB()\_size
header\data_bytes = size
WriteData(1, header, headersize)

; get and write raw-data of sound 1
SelectElement(SoundsDB(), 0)
FileSeek(0, SoundsDB()\_bias)
ReadData(0, *buffer, SoundsDB()\_size)
WriteData(1, *buffer, SoundsDB()\_size)

; get and write raw-data of sound 4
SelectElement(SoundsDB(), 3)
FileSeek(0, SoundsDB()\_bias)
ReadData(0, *buffer, SoundsDB()\_size)
WriteData(1, *buffer, SoundsDB()\_size)

CloseFile(0)
CloseFile(1)
FreeMemory(*buffer)
p.s.
It seems this is because incorrectly calculated the bias in "create_db.pb".

p.s. 2
Previous code fixed. Try again.
ZX80
Enthusiast
Enthusiast
Posts: 330
Joined: Mon Dec 12, 2016 1:37 pm

Re: wave-files (merge)

Post by ZX80 »

CELTIC88, wilbert, is it right or not?
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: wave-files (merge)

Post by wilbert »

What's your goal with it ?
If it's for your own personal use and it is working, it is fine.
If you want others to use it as well, you should do it differently and scan all chunks until you have found both the 'fmt ' and 'data' chunks.
Windows (x64)
Raspberry Pi OS (Arm64)
ZX80
Enthusiast
Enthusiast
Posts: 330
Joined: Mon Dec 12, 2016 1:37 pm

Re: wave-files (merge)

Post by ZX80 »

to wilbert
What's your goal with it ?
I already have a parser program for meteorological telegrams. Now I want to voice this program (without use SAPI). Like this or this.
If it's for your own personal use...
This is for personal use (one side), because no one will change the once created database of sounds (otherwise everything will be corrupted). However, a few people will use final vesion of program (another side).
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: wave-files (merge)

Post by wilbert »

That makes sense :)
For playback I wouldn't create wav files.
It's faster and easier to allocate memory, combine things in memory and use CatchSound / PlaySound.
Windows (x64)
Raspberry Pi OS (Arm64)
ZX80
Enthusiast
Enthusiast
Posts: 330
Joined: Mon Dec 12, 2016 1:37 pm

Re: wave-files (merge)

Post by ZX80 »

wilbert, in other words, you suggest keeping a directory containing 100-200 separate wav-files and playback them sequentially? It seemed to me that it would be better to create one output-file and send it to Windows Media Player. Anyway, thanks for advice.

upd:
Ooops. It seems I inattentively read your previous post. :oops:
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: wave-files (merge)

Post by wilbert »

ZX80 wrote:wilbert, in other words, you suggest keeping a directory containing 100-200 separate wav-files and playback them sequentially? It seemed to me that it would be better to create one output-file and send it to Windows Media Player. Anyway, thanks for advice.
No, that's not what I'm suggesting.
Combining all those files into one SoundsDB is a good idea :)

What I'm suggesting is that if you need to create a sentence from samples 5, 8, 2 and 7, you allocate memory for the total raw bytes and header.
You copy the header and raw bytes of those samples to the allocated memory, use CatchSound to catch the wav from memory and PlaySound to play it back.
I was assuming you would use PlaySound to play back the sentence.
But you are talking about sending it to Windows Media Player. If you play it back using an external application my suggestion of course won't work. In that case you need to create a wav file like you were already doing.
Windows (x64)
Raspberry Pi OS (Arm64)
ZX80
Enthusiast
Enthusiast
Posts: 330
Joined: Mon Dec 12, 2016 1:37 pm

Re: wave-files (merge)

Post by ZX80 »

play it back using an external application
Yes.
reason: result wav-file will take up a lot of space in memory. I afraid that program will crashed. Even if we assume that the memory will be free after merge all fragments(catchsound) and playback(PlaySound -> FreeSound) or after export to disk(whole immediately). So I chose way write file to disk "step by step" ("byte by byte").
Post Reply