none
How to draw a WAV sound file Chart graph and get the header data. RRS feed

  • General discussion

  • I have been learning WAV along with the questions asked on the forum lately. So I set out to draw the wav data in a chart.

    This is what I am using for the header info definition:

          http://soundfile.sapp.org/doc/WaveFormat/

    I have the thing working by converting the wav data byte array to integer array for plotting purposes. I am not much on hex. I laid out the header values from the integer array in the text box. For example integerarray(22) is the Number of Channels which tells me mono or stereo.

    My first question is if the integerarray has a value of 1, is that also 1 in hex as shown in the header data image in the link above?

    And can I use the integer data this way?

    I am realizing I have to draw different charts based on 16 bit or 24 bit wav, two channels, and... ??

    Green and Red graphs are left and right channels.

    So I am not sure if this is working? Anyone else interested in trying it? A group project?


    Is this what the graphs normally look like?

    Here is an 8 bit, mono wave that is actually the pacman monstereaten sound.

    And here is the start of Blondie's Rapture song. A 16 bit stereo.

    I am not sure what these wavs were sampled at I am going by the header info I am extracting.

    BTW you can zoom the chart by dragging the area you want. Then reset to zoom out.

    The chart is set to plot the first 2000 values in the wave file including the header.

    So I imagine I have missed something and did some other wrong?

    To make the sample just add the two buttons, label, chart and textbox to an empty form the code does the rest.

    Imports System.IO
    Imports System.Windows.Forms.DataVisualization.Charting
    
    Public Class Form3
        'Private SoundFilePath As String = "C:\test sounds\W_HWY.wav"
        Private SoundFilePath As String = "C:\test sounds\MonsterEaten.wav"
        'Private SoundFilePath As String = "C:\test sounds\test.wav"
    
        Private ByteArray() As Byte
        Private IntArray() As Integer
    
        Dim ChunkID As Integer
        Dim ChunkSize As Integer
        Dim Format As Integer
    
        Dim SubChunkID As Integer
        Dim SubChunkSize As Integer
        Dim AudioFormat As Integer    '1 = PCM
    
        Dim NumChannels As Integer   '1 = Mono  2 = Stereo
        Dim SampleRate As Integer
        Dim ByteRate As Integer
        Dim BlockAlign As Integer
        Dim BitsPerSample As Integer
    
    
        Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ClientSize = New Size(500, 500)
            BackColor = Color.DimGray
            Button1.Text = "Open..."
            Button2.Text = "Reset Zoom"
            Label1.Text = SoundFilePath
            Label1.ForeColor = Color.AntiqueWhite
            Label1.Font = New Font("tahoma", 10)
    
    
            InitilizeData()
            DrawData()
    
        End Sub
    
        Private Sub Form3_Resize(sender As Object, e As EventArgs) Handles Me.Resize
            Dim Border As Integer = 20
            Dim w As Integer = ClientSize.Width - (2 * Border)
    
            Chart1.Location = New Point(Border, Button1.Bottom + Border)
            Chart1.Size = New Size(w, 200)
    
            TextBox1.Location = New Point(Border, Chart1.Bottom + Border)
            TextBox1.Size = New Size(w, 200)
    
        End Sub
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Using d As New OpenFileDialog
                d.ShowDialog()
                If d.FileName <> "" Then
                    SoundFilePath = d.FileName
                    InitilizeData()
                    DrawData()
                End If
            End Using
        End Sub
    
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            'reset
            DrawData()
        End Sub
    
        Private Function ByteArrayToIntegerArray(ByVal ByteArray() As Byte) As Integer()
            Dim rv() As Integer = New Integer() {}
            If ByteArray.Length > 0 Then
                Array.Resize(rv, ByteArray.Length)
                Array.Copy(ByteArray, 0, rv, 0, rv.Length)
            End If
            Return rv
        End Function
    
        Private Sub InitilizeData()
            ByteArray = File.ReadAllBytes(SoundFilePath)
            IntArray = ByteArrayToIntegerArray(ByteArray)
    
            ChunkID = IntArray(0)
            ChunkSize = IntArray(4)
            Format = IntArray(8)
    
            SubChunkID = IntArray(12)
            SubChunkSize = IntArray(16)
            AudioFormat = IntArray(20)   '1 = PCM
    
            NumChannels = IntArray(22)   '1 = Mono  2 = Stereo
            SampleRate = IntArray(24)
            ByteRate = IntArray(28)
            BlockAlign = IntArray(32)
            BitsPerSample = IntArray(34)
    
        End Sub
    
        Private Sub DrawData()
            Label1.Text = SoundFilePath
    
    
            Dim s As String = ""
    
    
            s &= "ChunkID" & vbCrLf
            s &= IntArray(0) & vbCrLf
            s &= IntArray(1) & vbCrLf
            s &= IntArray(2) & vbCrLf
            s &= IntArray(3) & vbCrLf & vbCrLf
    
            s &= "ChunkSize" & vbCrLf
            s &= IntArray(4) & vbCrLf
            s &= IntArray(5) & vbCrLf
            s &= IntArray(6) & vbCrLf
            s &= IntArray(7) & vbCrLf & vbCrLf
    
            s &= "Format" & vbCrLf
            s &= IntArray(8) & vbCrLf
            s &= IntArray(9) & vbCrLf
            s &= IntArray(10) & vbCrLf
            s &= IntArray(11) & vbCrLf & vbCrLf
    
            s &= "SubChunkID" & vbCrLf
            s &= IntArray(12) & vbCrLf
            s &= IntArray(13) & vbCrLf
            s &= IntArray(14) & vbCrLf
            s &= IntArray(15) & vbCrLf & vbCrLf
    
            s &= "SubChunkSize" & vbCrLf
            s &= IntArray(16) & vbCrLf
            s &= IntArray(17) & vbCrLf
            s &= IntArray(18) & vbCrLf
            s &= IntArray(19) & vbCrLf & vbCrLf
    
            s &= "AudioFormat" & vbCrLf
            s &= IntArray(20) & vbCrLf
            s &= IntArray(21) & vbCrLf & vbCrLf
    
            s &= "NumChannels" & vbCrLf
            s &= IntArray(22) & vbCrLf
            s &= IntArray(23) & vbCrLf & vbCrLf
    
            s &= "SampleRate" & vbCrLf
            s &= IntArray(24) & vbCrLf
            s &= IntArray(25) & vbCrLf
            s &= IntArray(26) & vbCrLf
            s &= IntArray(27) & vbCrLf & vbCrLf
    
            s &= "ByteRate" & vbCrLf
            s &= IntArray(28) & vbCrLf
            s &= IntArray(29) & vbCrLf
            s &= IntArray(30) & vbCrLf
            s &= IntArray(31) & vbCrLf & vbCrLf
    
            s &= "BlockAlign" & vbCrLf
            s &= IntArray(32) & vbCrLf
            s &= IntArray(33) & vbCrLf & vbCrLf
    
            s &= "BitsPerSample" & vbCrLf
            s &= IntArray(34) & vbCrLf
            s &= IntArray(35) & vbCrLf
    
            TextBox1.Text = s
    
    
    
            With Chart1.ChartAreas(0)
                .AxisX.MajorGrid.LineColor = Color.DimGray
                .AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Dash
    
                .AxisY.MajorGrid.LineColor = Color.DimGray
                .AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Dash
    
                .AxisX.Minimum = 0
                .AxisY.Minimum = 0
    
                .AxisX.ScrollBar.Enabled = True
                .CursorX.IsUserEnabled = True
                .CursorX.IsUserSelectionEnabled = True
                .AxisX.ScaleView.Zoomable = True
                .AxisX.ScaleView.ZoomReset()
                .BackColor = Color.Black
            End With
    
            Chart1.Series.Clear()
            Chart1.Series.Add("Channel 1")
            Chart1.Series.Add("Channel 2")
    
            Dim max As Integer
    
            With Chart1.Series(0)
                .IsVisibleInLegend = False
                .ChartType = SeriesChartType.Line
                .Color = Color.LimeGreen
            End With
            With Chart1.Series(1)
                .IsVisibleInLegend = False
                .ChartType = SeriesChartType.Line
                .Color = Color.Red
            End With
    
            Dim BitStep As Integer
            Select Case BitsPerSample
                Case 16
                Case Else
    
            End Select
    
            Select Case NumChannels
                Case 2
                    max = IntArray.Length - 2
                    If max > 2000 Then max = 2000
    
                    Select Case BitsPerSample
                        Case 16
                            For x As Integer = 0 To max Step 4
                                Chart1.Series(0).Points.AddXY(x, IntArray(x) + IntArray(x + 1))
                                Chart1.Series(1).Points.AddXY(x, IntArray(x + 2) + IntArray(x + 3))
                            Next
                        Case Else
    
                    End Select
    
                Case Else
                    max = IntArray.Length - 1
                    If max > 2000 Then max = 2000
                    With Chart1.Series(0)
                        .IsVisibleInLegend = False
                        .ChartType = SeriesChartType.Line
                        .Color = Color.LimeGreen
                        Select Case BitsPerSample
                            Case 16
                                For x As Integer = 0 To max Step 2
                                    Chart1.Series(0).Points.AddXY(x, IntArray(x) + IntArray(x + 1))
                                Next
                            Case Else
                                For x As Integer = 0 To max
                                    Chart1.Series(0).Points.AddXY(x, IntArray(x))
                                Next
                        End Select
                    End With
            End Select
        End Sub
    End Class


    PS I imagine I should remove the header data from the chart graph, not sure if that will offset the charts I have shown improperly.

    PSS Nope if I start the graph at index 44 to remove the header I still get the same graph.


    • Edited by tommytwotrain Friday, March 2, 2018 2:23 AM add chart to title
    Sunday, February 25, 2018 5:55 PM

All replies

  • Hi Tommy,

    First it doesn't look like the byte data is being translated into the proper values.  Within the header portion of the file, some bytes represent String values and some represent Integer values so you have to be sure to combine the correct number of bytes to make each value translation.  Then when you get into the actual sound data you'll need to combine bytes into bigger numeric types based on the data extracted from the header (you also have to be concerned with endianness).  I expect to see a lot of BitConverter usage peppered in the code but haven't spotted any...

    Also, this doesn't look right to me (without regard to whether IntArray contains proper values):

                            For x As Integer = 0 To max Step 4
                                Chart1.Series(0).Points.AddXY(x, IntArray(x) + IntArray(x + 1))
                                Chart1.Series(1).Points.AddXY(x, IntArray(x + 2) + IntArray(x + 3))
                            Next

    The X-axis value for the point should be incrementing by 1, not by 4, correct?  I think you need a separate counter variable and array index variable.

    I've tried to find some decent reference material for you (which can be difficult).  This first link has a decent file structure layout:

    https://msdn.microsoft.com/en-us/library/windows/desktop/dd757929%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396

    This second one has a decent write-up:

    https://www.codeguru.com/cpp/g-m/multimedia/audio/article.php/c8935/PCM-Audio-and-Wave-Files.htm

    So you'll need to interpret the header bytes by converting them to the appropriate values (4 character strings and 32bit integers), then convert the WAVEFORMATEX data according to its layout (you'll need to analyze its internal values to determine which structure was used and then extract values accordingly).  You'll need all of that extracted header data to tell you how to then interpret the byte data that follows.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"


    Sunday, February 25, 2018 7:36 PM
    Moderator
  • Hi Reed!

    I am just trying something fun without needing all the refs but maybe it wont go that way.

    You said:

    "The X-axis value for the point should be incrementing by 1, not by 4, correct?  I think you need a separate counter variable and array index variable."

    Each point added on the x axis is just 1 chart point for the series ie green or red lines. I have not given the x axis units it is just x = index to the array 0, 1, 2... .

    So I am adding two values from the intarray for two bytes for 16 bit and then do that twice for left and right channels = 2 + 2 = 4. This if for 16 bit stereo. ??"???

    I am thinking the first 8 bit mono graph is correct. Although the y values are just the int byte values. What should that max be for the type? It is the pac man sound Devon posted one time I think some members have it. If anyone had a real graph we could test against that would be handy.

    Its just for fun so I have looked and yes heavy reading and I see the BitConverter.

    First off I am hoping to just understand the raw thing a bit first.

    Also, in the Zempco thread I thought for the scaling and etc it would be handy to see what the math does.

    Finally I wanted a peak level meter real time streaming but I suppose thats too much?

    So just a simple chart for visual and etc is what I am after of course maybe others would like more.

    Still looking at your other comments...


    Sunday, February 25, 2018 7:56 PM
  • It won't work to just add the two byte values together.  For a 16 bit sample you need to take the two bytes and convert them into an Int16 instance (the most significant byte shifted left 8 bits then OR'd with the less significant byte).  With stereo (and spatial/3D eg 7.1 surround) the additional channels (speakers) are interleaved in the data.

    8-bit samples will have 1 byte per data value.  The values will range from 0 to 255 with 128 being 0Db; 0-127 will be -Db and 129-255 will be +Db.  When you get into 16 bit and above, it changes to signed integers so 0 is 0Db and positive values are +Db and negative values are -Db.

    So, yes, the data bytes for an 8-bit mono recording will just be those individual byte values.  For 16 bit you'll need to use BitConverter (or binary math) to get Short values from every 2 bytes.  For 24 bit audio you need to use four bytes to get a 32 bit value in which the most significant byte will always be zero (three bytes from the data, padded with a zero byte).

    If you know the encoding parameters of the file you can skip the header bytes and go with the known values.  Just make sure you skip the correct number of bytes for the particular WAVEFORMATEX structure used.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Sunday, February 25, 2018 8:36 PM
    Moderator
  • Reed,

    Oh. I had seen some of that.

    Then I was thinking the integer I have now is the magnitude of the amplitude... say that 3 times... And etc.

    To keep it short I think I will see what happens when I scale the 8 bit integer value.

    After all, they are just a values of some units on a graph? There is a max and a min and we want to see it on the graph. Then scale the max min.

    What is the y value? I mean if you add the bytes correctly and the correct answer is 420, what is that 420 decibels or what is the amplitude? That's the volume right?

    So I keep thinking it does not matter what the value in the wav sound file is I will just scale the max min. But I guess they have to be added properly first... maybe I am way off?

    Sunday, February 25, 2018 9:58 PM
  • The Y value is a percentage of the max amplitude (yes, volume or "loudness").  If the number is 420 and its 16 bits, that's 420/65534 or 0.6% of the total loudness.  So the values are always just a percentage of the total range of values, based on the bit depth.

    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Sunday, February 25, 2018 11:38 PM
    Moderator
  • The Y value is a percentage of the max amplitude (yes, volume or "loudness").  If the number is 420 and its 16 bits, that's 420/65534 or 0.6% of the total loudness.  So the values are always just a percentage of the total range of values, based on the bit depth.

    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"


    Thanks Reed.

    So thats 16 ^ 4 = 65534 ? Is that for 1 channel?

    I guess my graph of 8 bits above that has a max y around 220 must be way off of the actual value which should be more like 8 ^ 4 = 4096 max??

    "0.6% of the total loudness"

    What is total loudness? Just an arbitrary decibel level set when recording or is it a standard amount for all wav files and recordings? Or is total loudness 655534 for 16 bit? Or is that just play back loudness?

    Where does decibel come in? Is that the total loudness too? 100 decibels = 655534? Not that simple I guess. Not linear.

    I have uploaded a copy of the edit: MonsterEaten.wav file to my web site beta page and a zip to download for those interested.

    http://sandiasoftware.com/beta/test.htm

    That is the file for the first 8 bit mono graph I show above.


    Monday, February 26, 2018 1:30 AM
  • So thats 16 ^ 4 = 65534 ? Is that for 1 channel?

    It's 2^16 because there are 16 bits per sample.  So the first byte needs to be left shifted 8 places before adding the second byte (or just multiply it by 2^8).  Or the other way around, depending on byte order.  Your channels are probably interleaved - that's the default for stereo. That is, two bytes of left channel data, two bytes of right channel data, and repeat, as per the above code.  That should be confimed in the header data, although not all formats include specific details of the channel information layout, and just assume a default. 

    Monday, February 26, 2018 2:01 AM
  • Yes, "loudness" is an arbitrary value based on recording levels.

    The final volume is determined by the amplifier in the sound card.  The actual volume is whatever the volume level is set to for the sound card.  So think of the byte data as representing some percentage of the currently selected volume level.  The more bits you have, the greater the "resolution" of the wave curve.  More bits equals more increments in loudness.

    So there is no direct correlation between actual decibel level and the data value.  The data value is simply a percentage of the total available decibel level based on where the volume is set and how powerful the sound card is.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Monday, February 26, 2018 2:42 AM
    Moderator
  • Adding the bit shift 2 ^ 8 puts things in the right range of y axis values 65536 for the 16 bit stereo wav sound. See image below.

    The 8 bit mono graph has not changed (see image above). I guess my graph of 8 bits above that has a max y around 220 must be way off of the actual value which should be more like 8 ^ 4 = 4096 max??

    So I guess I still need to convert from hex to integer differently? Or do I even need to convert? I guess I can work with the byte array? But now I dont get what a the byte array is I guess.

    I noticed that my integerarray values are the same as the bytearray values so I guess the function to convert byte to integer is doing nothing. So I guess I am just adding the byte values together. So I am not sure what is happening there. If that should be hex addition or something.

    The 16 bit wav is mostly drums so I am not sure if this is correct or not. But the max is around the correct max??

    I have updated the code above. The bytearray values for the header are show the textbox of the image below.

    Not sure if anyone is still interested.

    code removed to save space



    Monday, February 26, 2018 2:06 PM
  • Its binary so it is always 2^bits.

    For 8 bit it is 2^8 so the max value is 255 (max value for a byte).

    Once you have gotten past the header data, the rest of the bytes should be graphed with the X-axis at 127 so that values 0-126 are below the X-axis and values 128-255 are above the X-axis.  Each byte would be plotted 1 increment apart.

    I'll see if I can pull your file and make an example.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Monday, February 26, 2018 2:28 PM
    Moderator
  • Hey 2Train you may want to download or review code here Sound scanner and FFT analyzerat CodeProject . There"s 2 .Dlls but one called Port is C++ I believe as it can not be decompiled using Telerik Just Decompile and the other can be decompiled. Anyhoe the math in it would be right up ur alley..

    I believe the port .dll is for accessing the hardwares audio stream for output to speakers or maybe input from mic.


    La vida loca


    Monday, February 26, 2018 3:19 PM
  • Its binary so it is always 2^bits.

    For 8 bit it is 2^8 so the max value is 255 (max value for a byte).

    Once you have gotten past the header data, the rest of the bytes should be graphed with the X-axis at 127 so that values 0-126 are below the X-axis and values 128-255 are above the X-axis.  Each byte would be plotted 1 increment apart.

    I'll see if I can pull your file and make an example.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Reed,

    Ok thanks, I have to study that.

    Do you have real software to make the graph to compare? That would help me I think. The software I have does not run anymore on win 64 bit which is all I have now that still runs. Last I messed with audio video was 15 years ago. Although I could try to start my old Sony with the bad hard drive to do it but was hoping someone else has it.

    I will add the 16 bit sample I am using to may web site if you like. The one on my web site now is the 8 bit pacman sound.

    PS Do you mean the Y axis should be +- 127. That is what I thought sort of. My graph has the symmetrical shape but its all positive. I have figured that was wrong. But first I need to get the positive half right and then it seems you just flip it for the other half. But prob not that easy? Oh wait, you mean my graph of the pacman 8 bit is more or less correct but I need to add it correctly then it shifts down -127? Ok I will wait for you to look. I have errands to run...

    :)

    Monday, February 26, 2018 3:34 PM
  • PS Both wav files are now on my beta site for those that would like to download them.

    http://sandiasoftware.com/beta/test.htm

    There is the 8 bit mono pacman eaten and the 16 bit stereo blondie rapture wav files.

    Monday, February 26, 2018 3:48 PM
  • Do you have real software to make the graph to compare? 

    That's what I planned to write when I get a chance. :)  I don't have anything readily available to provide.

    It will be later today until I have time to make any code examples, but in the meantime, see if this terrible drawing helps. :P

    In this example I show the min/max amplitude values at 8 and 16 bits and then show an arbitrary point on the curve which is at half of max amplitude (with the value at 8 bits and 16 bits).  Hopefully this shows how the bit depth is simply the number of increments on the Y-Axis.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Monday, February 26, 2018 3:53 PM
    Moderator
  • Ok that drawing is helpful Reed thanks.

    Meanwhile here is what they look like in adobe premeire which I forget how to use to show them very well. I guess my good sound software is on my old ME system where my real studio was. It still runs...

    8 - mono pacman eaten  - this is the full wav I think.

    This is blondie rapture 16 bit stereo just the first part. Now that's what I thought it should look like.



    Monday, February 26, 2018 5:57 PM
  • Wow.  Unreal.  I didn't notice the graph object.  Made that a built easier than drawing it in a picturebox.

    Been trying to figure out some of the data below it.  Says channel number is 70+ and the Samplerate  and Audio format are high too. Are they just adding 2 channels together?

    I hope to find my issue soon.  I don't think I will ever worry about input.  But know whe3 to find some help and ideas!!!

    Monday, February 26, 2018 9:41 PM
  • Very cool.  I guess I could use something like that on my output.  Metering maybe.  Not sure you guys have figured out want the app I have is actually for yet.
    Monday, February 26, 2018 9:59 PM
  • Oh, sorry, I was showing the monstereaten sound before.

    This is the latest v3 code above with the pacmaneaten full wav. Looks good except for the scales and things.


     That data does not look right to me.  However,  the audio data drawing looks pretty close.  Here is what the PacmanEaten.wav looks like in Audacity.

     

     And this is what the MonsterEaten.wav looks like...

     

     I have some code below which will load the data for a PCM or Extensible format wave file that you can test out if you want.

    Public Class Form1
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Using ofd As New OpenFileDialog
                ofd.Filter = "Wave Files|*.wav"
                If ofd.ShowDialog = DialogResult.OK Then
                    Dim wfd As New WaveFileData(ofd.FileName)
                    Label1.Text = wfd.ToString
                End If
            End Using
        End Sub
    End Class

    Public Class WaveFileData
        Public ReadOnly Property FormatTag As FormatTags
        Public ReadOnly Property Channels As Integer
        Public ReadOnly Property SamplesPerSec As Integer
        Public ReadOnly Property AvgBytesPerSec As Integer
        Public ReadOnly Property BlockAlign As Integer
        Public ReadOnly Property BitsPerSample As Integer

        'These 4 properties are only used if the FormatTag is other than FormatTags.PCM
        Public ReadOnly Property cbSize As Integer
        Public ReadOnly Property ValidBitsPerSample As UShort
        Public ReadOnly Property ChannelMask As SpeakerFlags
        Public ReadOnly Property SubFormat As Guid

        Public ReadOnly Property RawWaveData As Byte()

        Public ReadOnly Property DurationInSeconds As Double
            Get
                Return (RawWaveData.Length / AvgBytesPerSec)
            End Get
        End Property

        Public Sub New(filename As String)
            Dim FormatFound As Boolean = False
            Using fs As New IO.FileStream(filename, IO.FileMode.Open), br As New IO.BinaryReader(fs)
                br.BaseStream.Position = 8 'skip past 'RIFF' and the (file size - 8) integer value
                If System.Text.Encoding.ASCII.GetString(br.ReadBytes(4)) = "WAVE" Then 'make sure it is a 'WAVE' file
                    For i As Integer = 12 To CInt(fs.Length) - 21
                        br.BaseStream.Position = i
                        If Not FormatFound AndAlso System.Text.Encoding.ASCII.GetString(br.ReadBytes(4)) = "fmt " Then 'look for 'fmt ' chunk which contains the WAVEFORMATEX info
                            br.BaseStream.Position += 4 'skips past the 4 byte integer, used for the size of the chunck
                            FormatTag = CType(br.ReadUInt16, FormatTags)
                            Channels = br.ReadUInt16
                            SamplesPerSec = br.ReadInt32
                            AvgBytesPerSec = br.ReadInt32
                            BlockAlign = br.ReadUInt16
                            BitsPerSample = br.ReadUInt16
                            If FormatTag <> FormatTags.PCM Then
                                cbSize = br.ReadUInt16
                                If cbSize = 22 AndAlso FormatTag = FormatTags.EXTENSIBLE Then
                                    ValidBitsPerSample = br.ReadUInt16
                                    ChannelMask = CType(br.ReadInt32, SpeakerFlags)
                                    SubFormat = New Guid(br.ReadBytes(16))
                                    FormatFound = True
                                End If
                            Else
                                FormatFound = True
                            End If
                        ElseIf FormatFound AndAlso System.Text.Encoding.ASCII.GetString(br.ReadBytes(4)) = "data" Then 'look for 'data' chunck, the raw wave data
                            Dim RawWaveDataLength As Integer = br.ReadInt32 'get raw wave data length in bytes
                            RawWaveData = br.ReadBytes(RawWaveDataLength)
                            Exit For
                        End If
                    Next
                End If
            End Using
        End Sub

        Public Overrides Function ToString() As String
            Dim sb As New System.Text.StringBuilder
            sb.AppendLine("FormatTag: " & FormatTag.ToString)
            sb.AppendLine("Channels: " & Channels.ToString)
            sb.AppendLine("SamplesPerSec: " & SamplesPerSec.ToString)
            sb.AppendLine("AvgBytesPerSec: " & AvgBytesPerSec.ToString)
            sb.AppendLine("BlockAlign: " & BlockAlign.ToString)
            sb.AppendLine("BitsPerSample: " & BitsPerSample.ToString)
            If FormatTag = FormatTags.EXTENSIBLE Then
                sb.AppendLine("cbSize: " & cbSize.ToString)
                sb.AppendLine("ValidBitsPerSample: " & ValidBitsPerSample.ToString)
                sb.AppendLine("ChannelMask: " & ChannelMask.ToString)
                sb.AppendLine("SubFormat: " & SubFormat.ToString)
            End If
            sb.AppendLine("RawWaveDataLength: " & RawWaveData.Length.ToString)
            sb.Append("DurationInSeconds: " & DurationInSeconds.ToString)
            Return sb.ToString
        End Function

        Public Enum FormatTags As UShort
            PCM = &H1
            IEEE_FLOAT = &H3
            ALAW = &H6
            MULAW = &H7
            EXTENSIBLE = &HFFFE
        End Enum

        <Flags>
        Public Enum SpeakerFlags As Integer
            SPEAKER_FRONT_LEFT = &H1
            SPEAKER_FRONT_RIGHT = &H2
            SPEAKER_FRONT_CENTER = &H4
            SPEAKER_LOW_FREQUENCY = &H8
            SPEAKER_BACK_LEFT = &H10
            SPEAKER_BACK_RIGHT = &H20
            SPEAKER_FRONT_LEFT_OF_CENTER = &H40
            SPEAKER_FRONT_RIGHT_OF_CENTER = &H80
            SPEAKER_BACK_CENTER = &H100
            SPEAKER_SIDE_LEFT = &H200
            SPEAKER_SIDE_RIGHT = &H400
            SPEAKER_TOP_CENTER = &H800
            SPEAKER_TOP_FRONT_LEFT = &H1000
            SPEAKER_TOP_FRONT_CENTER = &H2000
            SPEAKER_TOP_FRONT_RIGHT = &H4000
            SPEAKER_TOP_BACK_LEFT = &H8000
            SPEAKER_TOP_BACK_CENTER = &H10000
            SPEAKER_TOP_BACK_RIGHT = &H20000
            SPEAKER_RESERVED = &H7FFC0000 ' Bit mask locations reserved for future use
            SPEAKER_ALL = &H80000000 ' Used to specify that any possible permutation of speaker configurations
        End Enum

        '#### I have only added these for comparing the (SubFormat) to see what it is, I don't actually use them (yet). ####
        'These are only the 6 typical subtypes listed in the msdn document for the WAVEFORMATEXTENSIBLE structure >>https://msdn.microsoft.com/en-us/library/dd757714%28v=vs.85%29.aspx
        'These are the GUID's for the (SubFormat) property of this class.
        Public KSDATAFORMAT_SUBTYPE_PCM As New Guid("00000001-0000-0010-8000-00aa00389b71")
        Public KSDATAFORMAT_SUBTYPE_ADPCM As New Guid("00000002-0000-0010-8000-00aa00389b71")
        Public KSDATAFORMAT_SUBTYPE_IEEE_FLOAT As New Guid("00000003-0000-0010-8000-00aa00389b71")
        Public KSDATAFORMAT_SUBTYPE_ALAW As New Guid("00000006-0000-0010-8000-00aa00389b71")
        Public KSDATAFORMAT_SUBTYPE_MULAW As New Guid("00000007-0000-0010-8000-00aa00389b71")
        Public KSDATAFORMAT_SUBTYPE_DRM As New Guid("00000009-0000-0010-8000-00aa00389b71")
    End Class
     

     Here are two files,  the MonsterEaten.wav file and a 24bit file which uses the WAVEFORMATEXTENSIBLE structure instead of just the WAVEFORMATEX structure as the MonsterEaten.wav file does.

     

     Anyways,  very interesting topic.  I will have to see if i can throw something together to draw the wave data for different formats.   8)


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Tuesday, February 27, 2018 1:16 AM
    Tuesday, February 27, 2018 12:33 AM
  •  Ooops... I forgot to include the enumeration for the SpeakerFlags.  I just edited the code and added it.  8)

    If you say it can`t be done then i`ll try it

    Tuesday, February 27, 2018 1:17 AM
  • Hi Razerz!

    Looks pretty close.

    The header info in the textbox shown right now is just the raw bytearray numbers. I am not sure what to do with them although I think Reed and Acamar have pointed out some of what it needs. I dont see what they are it should spell riff as in the first link I guess but I dont know?

    So I am just trying to make a simple example of the wav data somehow.

    Give me some time to try your code.

    :)

    Tuesday, February 27, 2018 1:49 AM
  •  Yes,  a wave file is a RIFF file type which has different parts of the file put in different 'Chunks' in the file.  If you look at my code you will see where I skip past the 'RIFF' and the 'File Size' and check if the next 4 bytes after that are the 4 ascii values for the string "WAVE" which indicates it is a wave file type riff file.  Then you can see I look for the 'fmt ' chunk and 'data' chunk to get format data and raw wave data in the WaveFileData class.

     As Reed has said,  the type of raw data can be a single (Byte) per channel,  2 byte (Short) per channel,  3 byte per channel for a 24bit which needs to be converted special,  or a 4 byte (Single).  So,  you have to read the wave data bytes correctly for the file format in order to get the correct values.  You also have to use the Channels and/or BlockAlighn to know how to read the data too.  It can get a little tricky to be able to read any file you choose and decipher the correct way to read and convert the raw wave data bytes into the correct values.

     I am out of here for tonight so,  talk at ya later.  8)


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Tuesday, February 27, 2018 2:25 AM
    Tuesday, February 27, 2018 2:24 AM
  •  Yes,  a wave file is a RIFF file type which has different parts of the file put in different 'Chunks' in the file.  If you look at my code you will see where I skip past the 'RIFF' and the 'File Size' and check if the next 4 bytes after that are the 4 ascii values for the string "WAVE" which indicates it is a wave file type riff file.  Then you can see I look for the 'fmt ' chunk and 'data' chunk to get format data and raw wave data in the WaveFileData class.

     As Reed has said,  the type of raw data can be a single (Byte) per channel,  2 byte (Short) per channel,  3 byte per channel for a 24bit which needs to be converted special,  or a 4 byte (Single).  So,  you have to read the wave data bytes correctly for the file format in order to get the correct values.  You also have to use the Channels and/or BlockAlighn to know how to read the data too.  It can get a little tricky to be able to read any file you choose and decipher the correct way to read and convert the raw wave data bytes into the correct values.

     I am out of here for tonight so,  talk at ya later.  8)


    If you say it can`t be done then i`ll try it

    Oh. I am still stuck on the first step.

    The example reads the wav file into the  bytearray using read all bytes.

    So then I show the 8 bit mono monstereaten values bytearray(0) to (3) in the text box for ChunkID and the values in the bytearray are 82 73 70 70 as shown in the image of the example.

    But in the chart below it is shown as hex values  52 49 46 46 and that spells RIFF ???

    I suppose someone explained but I dont even know the most basic parts without spending a lot of time learning the basics of something like Little Big Man iloveginnvectors and such. Its just a fun thing for me not required. Although I was hoping to lay it out real simple like just for future generations.  :)

    Image from http://soundfile.sapp.org/doc/WaveFormat/


    Tuesday, February 27, 2018 3:27 AM
  • I haven't gone through your code line by line but it appears you are skipping the initial wave header data.

    Here's a little routine to take the MonsterEaten.wav and display its raw byte data as a hex string with 24 bytes per line, just like the example chart you posted.

    Public Class Form1
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            RichTextBox1.Font = New Font("Consolas", 9.0!)
            Dim rawWaveData() As Byte = IO.File.ReadAllBytes("E:\reedk\Downloads\MonsterEaten\MonsterEaten.wav")
            Dim rawWaveDataHex As New Text.StringBuilder
            For i = 0 To rawWaveData.Length - 1
                If i > 0 Then
                    If i Mod 24 = 0 Then
                        rawWaveDataHex.AppendLine()
                    Else
                        rawWaveDataHex.Append(" ")
                    End If
                End If
                rawWaveDataHex.Append(rawWaveData(i).ToString("X2"))
            Next
            RichTextBox1.Text = rawWaveDataHex.ToString
        End Sub
    End Class

    The first four lines of the output look like this:

    52 49 46 46 04 19 00 00 57 41 56 45 66 6D 74 20 10 00 00 00 01 00 01 00
    11 2B 00 00 11 2B 00 00 01 00 08 00 64 61 74 61 B4 18 00 00 80 7F 80 7F
    80 7F 80 7F 80 7F 80 7F 80 7F 80 7F 80 7F 80 7F 80 7F 80 7F 80 80 B0 D6
    DB DD DA D9 D6 D4 D1 D0 CD CC C9 C8 C5 C4 C2 C0 BE BD BB BB B9 B9 B8 B7

    Just like the picture, the first for bytes are the ASCII characters "RIFF", the next four are the chunk size for this file (6,404), and then four more ASCII characters spelling "WAVE".

    Here is an extension to the example code above.  Now the code will output the converted header information and then the raw byte data:

    Public Class Form1
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            RichTextBox1.Font = New Font("Consolas", 9.0!)
            Dim rawWaveData() As Byte = IO.File.ReadAllBytes("E:\reedk\Downloads\MonsterEaten\MonsterEaten.wav")
            RichTextBox1.Text = ByteDataAsWaveHeader(rawWaveData)
            RichTextBox1.AppendText(ControlChars.NewLine)
            RichTextBox1.AppendText(ByteDataAsHexString(rawWaveData))
        End Sub
    
        Private Function ByteDataAsHexString(rawBytes As IEnumerable(Of Byte)) As String
            Dim rawWaveDataHex As New Text.StringBuilder
            For i = 0 To rawBytes.Count - 1
                If i > 0 Then
                    If i Mod 24 = 0 Then
                        rawWaveDataHex.AppendLine()
                    Else
                        rawWaveDataHex.Append(" ")
                    End If
                End If
                rawWaveDataHex.Append(rawBytes(i).ToString("X2"))
            Next
            Return rawWaveDataHex.ToString
        End Function
    
        Private Function ByteDataAsWaveHeader(rawBytes As IEnumerable(Of Byte)) As String
            Dim waveHeader As New Text.StringBuilder
            waveHeader.Append("Chunk Descriptor: ") 'four bytes (0-3), character array
            waveHeader.Append(New String((From b In rawBytes.Take(4) Select Chr(b)).ToArray))
            waveHeader.AppendLine()
            waveHeader.Append("Chunk Size: ") 'four bytes (4-7), int32
            waveHeader.Append(BitConverter.ToInt32(rawBytes, 4))
            waveHeader.AppendLine()
            waveHeader.Append("Format Identifier: ") 'four bytes (8-11), character array
            waveHeader.Append(New String((From b In rawBytes.Skip(8).Take(4) Select Chr(b)).ToArray))
            waveHeader.AppendLine()
            waveHeader.Append("Subchunk Format: ") 'four bytes (12-15), character array
            waveHeader.Append(New String((From b In rawBytes.Skip(12).Take(4) Select Chr(b)).ToArray))
            waveHeader.AppendLine()
            waveHeader.Append("Subchunk 1 Size: ") 'four bytes (16-19), int32
            waveHeader.Append(BitConverter.ToInt32(rawBytes, 16))
            waveHeader.AppendLine()
            waveHeader.Append("Audio Format: ") 'two bytes (20,21), int16
            waveHeader.Append(BitConverter.ToInt16(rawBytes, 20))
            waveHeader.AppendLine()
            waveHeader.Append("Channel Count: ") 'two bytes (22,23), int16
            waveHeader.Append(BitConverter.ToInt16(rawBytes, 22))
            waveHeader.AppendLine()
            waveHeader.Append("Sample Rate: ") 'four bytes (24-27), int32
            waveHeader.Append(BitConverter.ToInt32(rawBytes, 24))
            waveHeader.AppendLine()
            waveHeader.Append("Byte Rate: ") 'four bytes (28-31), int32
            waveHeader.Append(BitConverter.ToInt32(rawBytes, 28))
            waveHeader.AppendLine()
            waveHeader.Append("Block Align: ") 'two bytes (32,33), int16
            waveHeader.Append(BitConverter.ToInt16(rawBytes, 32))
            waveHeader.AppendLine()
            waveHeader.Append("Bits per Sample: ") 'two bytes (34,35), int16
            waveHeader.Append(BitConverter.ToInt16(rawBytes, 34))
            waveHeader.AppendLine()
            waveHeader.Append("Data Identifier: ") 'four bytes (36-39), character array
            waveHeader.Append(New String((From b In rawBytes.Skip(36).Take(4) Select Chr(b)).ToArray))
            waveHeader.AppendLine()
            waveHeader.Append("Subchunk 2 Size: ") 'four bytes (40-43), int32
            waveHeader.Append(BitConverter.ToInt32(rawBytes, 40))
            waveHeader.AppendLine()
            'End of header; sample data begins at byte number 44
            Return waveHeader.ToString
        End Function
    End Class
    

    So to fill the chart with data, you should start at byte index 44 and then plot each byte one at a time, with the X-Axis located at 127.  So the Y-Axis values run from 0 (-128) at the bottom to 255 (+127) at the top.  The center point for the X-Axis is 127 (0).  This weird conversion is only needed with 8-bit data because the data type is an unsigned byte, but we need to represent negative values (a signed byte) to make a proper wave form (amplitude has to rise above zero then dip below zero).

    When you know you have a 16 bit encoded file, you skip the first 44 header bytes and then use BitConverter to convert the rest of the data, two bytes at a time, to a signed Int16 value.  This value can then be plotted on a Y-Axis with -32768 at the bottom, +32767 at the top and the X-Axis located at 0.

    Is this helping/making more sense?


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, February 27, 2018 2:38 PM
    Moderator
  • Reed,

    I see. The difference is rawWaveDataHex. You convert the values to hex. That is what I did not.

    When you dim xx as byte = readallbytes() what is that? An array of bytes. I guess I don't fully understand what a byte is. 8 bits. a bit is 1 or 0. So when put the value in xx array of bytes into the textbox and it is 70, what is that? 70 bytes? No. 70 what bits? No. 70 hex? No.

    Can someone explain that little detail please. I think that is where the confusion is. What is byte and what is hex and then when we plot the bytearray values above 44 what is that number? A byte. What is it? Hex? Integer? Binary. Can you just do one value by hand? ie how does the bytearray value of 82 get to be 52? I see the function you use mod this... what is that? ByteDataAsHexString what is a hex string? What is 52 a string? An integer?

    See the confusion? Its not for me. Its for future generations. I half get it I just cant explain it.

    :)

    PS I will add your  code to the example in a while.

    PS How does 52 = R ? Is that ascii chr(52) = "R" ?

    PS so only the header data 0-43 gets converted to hex? And 44- the end remains byte. That's an integer?

    PS thanks !

    PS Maybe it is too involved to explain easily in this thread. If so it will just have to be up to the coder to learn it. For example I would not ask, knowing what it is, you to explain what cosine is. One just has to learn that part and then one can use things that use cosine.



    Tuesday, February 27, 2018 3:16 PM
  • OK, so the confusion all stems from understanding base numbering systems (radix).

    A Byte is an unsigned (all positive values, no negative values) 8 bit number.  So that's 00000000 to 11111111 in binary.  In decimal that is 0 to 255.  In Hex that is 00 to FF.

    The later versions of Calc.exe in Windows have different views, switch to Programmer view and you can enter and convert numbers through all the various bases (base 2 or binary, base 8 or oct, base 10 or decimal, base 16 or hex).

    So Dim xx() As Byte = ReadAllBytes() is returning an array of single byte values.  Each byte value will be a number between 0 and 255 in decimal - which can be converted to a value between 00 and FF in hex.  I only did the hex conversion so that you could see the values matched the picture of the wave header that you posted.

    Since Hex notation is Base16 there are 16 numeric identifiers.  Since we run out of digits after 9, the remaining identifiers are the letters A thru F.  So we get 16 digits with:

    0 1 2 3 4 5 6 7 8 9 A B C D E F

    Since we need to use letters for hex values we have to use the String data type in code to represent the hex value.

    Lets look at the decimal number 42.

    In base 10 that is, from left to right, (4 * 10^1) + (2 * 10^0).

    If we were looking at the hexadecimal number 42, in base 16, that would be:

    (4 * 16^1) + (2 * 16^0) = 66 dec

    We can't look at the digits 42 as binary because those digits are out of range.  However, going the other way, decimal 42 equals 00101010 in binary:

    (0*2^7)+(0*2^6)+(1*2^5)+(0*2^4)+(1*2^3)+(0*2^2)+(1*2^1)+(0*2^0) = 42 dec

    I'm going to stop here and see how much sense this makes.  If I need to explain more, please let me know.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, February 27, 2018 4:13 PM
    Moderator
  • PS How does 52 = R ? Is that ascii chr(52) = "R" ?

    PS so only the header data 0-43 gets converted to hex? And 44- the end remains byte. That's an integer?



    Yes, the data bytes that represent words (RIFF, WAVE, etc) are ASCII conversions of the bytes.  You can see the example code uses CHR() for the output of any header data that is text.

    I only converted the header to hex to match the other output you were looking at.  There is no NEED to convert anything to Hex.  Hex is simply another way of looking at the numeric values.  As mentioned above, a byte can be represented with integers 0 thru 255 since that is the maximum range of an 8-bit value.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, February 27, 2018 4:19 PM
    Moderator
  • I have added Reed's header info to the example and fancied it up a bit.

    Question: I want to just use the bytearray(22) value for my variable NumChannels to set mono or stereo. So in monstereaten.wav the value is 70. And that is mono I guess??? But the blondie stereo bytearray(22) = 2 and thats stereo. I figure mono would be 1 ? Any insight there? Need to convert to hex? or can I use 70 for mono and 2 for stereo?

    Here is the latest example v5. I have centered the mono graph by subtracting 127. I have not changed the stereo graph. So only 8 bit mono wave files plot correctly in this version.

    This example makes the controls so just cut and paste to an empty form.

    PS this example has a Play button to play the wav on your speakers.

    Edit: see version 7 example below.



    Tuesday, February 27, 2018 6:18 PM
  • The channel count is a 16 bit value, so ByteArray(22) is only one of two bytes of data containing the channel count.  You need to use BitConverter to convert ByteArray(22) and ByteArray(23) into an Int16.  The resulting value should be either 1 or 2 for the example wave files.

    See the comments I put next to each header value?  Those comments show how many bytes it takes to read the value and what type of conversion you need to use on those bytes.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, February 27, 2018 6:23 PM
    Moderator
  •  Hey Tom,

     You looking at values in a hex format (52) which as an Integer value is (82).

    Chr(82) = 'R'

    or

    Chr(&H52) = 'R'

     As Reed said,  the byte values you are looking at are Integer values from 0 to 255.  You can use the microsoft calculator to see the Integer values of hex,  or hex values of Integers.  For example, set the radio buttons on the calc app so it is showing a Byte in a Decimal value.  Then enter 82 in and then you can switch back and forth to see the hex value of the number....

     

     You can see the 8 bits as shown below.  You can even click on the bits to turn them on/off if you want to see how they effect the number.

     

     As far as knowing which bytes should be read and grouped together to make a String,  4 byte Integer,  2 byte Short,  or whatever is all in understanding how a Riff file is put together.  For example,  for a riff file,  the first 4 bytes is always a String 'RIFF' followed by 4 bytes that should be read as a UInteger and is the files full size in bytes minus 8.  Then the next 4 bytes would be a String that tells what type of riff file it is.  In this case it is 'WAVE'.

     Then the rest needs to be read carefully to find the correct chunks of info/data.  They are not all always in the same order or position in a file, some files may include other data like the 'IART' (Artist Name) or several other different chunks of data.  So,  you can not just start reading data for any of these chunks at a fixed spot in every file,  with the exception of the first 12 bytes i described above.  You need to find the chunks as you read through the file.

     However,  i think you knew most of that already but,  you know i like to ramble on.... 8)

     


    If you say it can`t be done then i`ll try it

    Tuesday, February 27, 2018 6:53 PM
  • Its binary so it is always 2^bits.

    For 8 bit it is 2^8 so the max value is 255 (max value for a byte).

    Once you have gotten past the header data, the rest of the bytes should be graphed with the X-axis at 127 so that values 0-126 are below the X-axis and values 128-255 are above the X-axis.  Each byte would be plotted 1 increment apart.

    I'll see if I can pull your file and make an example.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Reed,

    Ok thanks, I have to study that.

    Do you have real software to make the graph to compare? That would help me I think. The software I have does not run anymore on win 64 bit which is all I have now that still runs. Last I messed with audio video was 15 years ago. Although I could try to start my old Sony with the bad hard drive to do it but was hoping someone else has it.

    I will add the 16 bit sample I am using to may web site if you like. The one on my web site now is the 8 bit pacman sound.

    PS Do you mean the Y axis should be +- 127. That is what I thought sort of. My graph has the symmetrical shape but its all positive. I have figured that was wrong. But first I need to get the positive half right and then it seems you just flip it for the other half. But prob not that easy? Oh wait, you mean my graph of the pacman 8 bit is more or less correct but I need to add it correctly then it shifts down -127? Ok I will wait for you to look. I have errands to run...

    :)

    You can download Audacity which is free for viewing audio tracks graphed. It's also capable of saving a displayed track by exporting to a different audio type such as .wav, .mp3, etc. Does stereo, mono, different sample rates, etc.

    La vida loca

    Tuesday, February 27, 2018 11:49 PM
  • Here is version 7 of the example. It will plot 8 bit mono and 16 bit stereo. You can zoom and scroll the chart by dragging the lmb in the chart area.

    The example makes the controls so all you need to do to run it is paste the code into an empty form.

    The wav files in the discussion are available here for a limited time.

    'chart wav sound file and list header info v7 'lmb drag the chart area to zoom Imports System.IO Imports System.Windows.Forms.DataVisualization.Charting Public Class Form5 Private BackClr As Color = Color.Black Private ForeClr As Color = Color.AntiqueWhite Private cBorder As Integer = 10 Private WithEvents OpenBtn As New Button With {.Parent = Me, .Text = "Open", .Location = New Point(20, cBorder), .ForeColor = ForeClr, .BackColor = BackClr} Private WithEvents PlayBtn As New Button With {.Parent = Me, .Text = "Play", .Location = New Point(100, cBorder), .ForeColor = ForeClr, .BackColor = BackClr} Private WithEvents ZoomResetBtn As New Button With {.Parent = Me, .Text = "Zoom Out", .Location = New Point(180, cBorder), .ForeColor = ForeClr, .BackColor = BackClr} Private WithEvents PathLabel As New Label With {.Parent = Me, .Font = New Font("tahoma", 10), .Location = New Point(270, cBorder), .ForeColor = ForeClr, .BackColor = Color.Transparent, .AutoSize = True} Private WithEvents Chart1 As New DataVisualization.Charting.Chart With {.Parent = Me, .Location = New Point(cBorder, 50), .Size = New Size(200, 150)} Private WithEvents Chart2 As New DataVisualization.Charting.Chart With {.Parent = Me, .Location = New Point(cBorder, Chart1.Bottom), .Size = New Size(200, 150)} Private WithEvents HeaderRtb As New RichTextBox With {.Parent = Me, .Font = New Font("courier new", 10), .Multiline = True, .Location = New Point(cBorder, Chart2.Bottom + 2 * cBorder), .ForeColor = ForeClr, .BackColor = BackClr} Private SoundFilePath As String Private ByteArray() As Byte
    Private NumChannels As Integer
        Private BitsPerSample As Integer

    Private Sub Form5_Load(sender As Object, e As EventArgs) Handles MyBase.Load ClientSize = New Size(500, 600) BackColor = Color.DimGray Text = "Draw Wav" Chart1.ChartAreas.Add("Left") Chart2.ChartAreas.Add("Right") InitilizeChartAreas(Chart1) InitilizeChartAreas(Chart2) InitilizeData() DrawChart() End Sub Private Sub Form5_Resize(sender As Object, e As EventArgs) Handles Me.Resize Dim w As Integer = ClientSize.Width - (2 * cBorder) Chart1.Width = w Chart2.Width = w If Chart2.Visible Then HeaderRtb.Top = Chart2.Bottom + cBorder Else HeaderRtb.Top = Chart1.Bottom + cBorder End If HeaderRtb.Size = New Size(w, ClientSize.Height - (HeaderRtb.Top + cBorder)) End Sub Private Sub OpenBtn_Click(sender As Object, e As EventArgs) Handles OpenBtn.Click Using d As New OpenFileDialog d.ShowDialog() If d.FileName <> "" Then SoundFilePath = d.FileName InitilizeData() DrawChart() End If End Using End Sub Private Sub PlayBtn_Click(sender As Object, e As EventArgs) Handles PlayBtn.Click 'play the current wav If ByteArray IsNot Nothing AndAlso ByteArray.Length > 0 Then Using MSWav As IO.MemoryStream = New IO.MemoryStream(ByteArray), SP As New Media.SoundPlayer(MSWav) MSWav.Position = 0 SP.Play() End Using End If End Sub Private Sub ZoomResetBtn_Click(sender As Object, e As EventArgs) Handles ZoomResetBtn.Click 'reset zoom Chart1.ChartAreas(0).AxisX.ScaleView.ZoomReset() Chart2.ChartAreas(0).AxisX.ScaleView.ZoomReset() DrawChart() End Sub Private Sub InitilizeData() If SoundFilePath <> "" Then 'read the wav file and make the header data Cursor = Cursors.WaitCursor PathLabel.Text = SoundFilePath ByteArray = File.ReadAllBytes(SoundFilePath) HeaderRtb.Text = ByteDataAsWaveHeader(ByteArray) HeaderRtb.AppendText(ControlChars.NewLine) HeaderRtb.AppendText(ByteDataAsHexString(ByteArray)) NumChannels = BitConverter.ToInt16(CType(ByteArray, Byte()), 22) BitsPerSample = BitConverter.ToInt16(CType(ByteArray, Byte()), 34) If NumChannels = 1 Then Chart2.Visible = False Else Chart2.Visible = True Form5_Resize(0, Nothing) Cursor = Cursors.Default End If End Sub Private Sub InitilizeChartAreas(thisChart As Chart) thisChart.BackColor = Color.FromArgb(144, 144, 144) thisChart.BackGradientStyle = System.Windows.Forms.DataVisualization.Charting.GradientStyle.TopBottom thisChart.BackSecondaryColor = System.Drawing.Color.Black thisChart.BorderlineColor = Color.FromArgb(26, 59, 105) thisChart.BorderlineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Solid thisChart.BorderlineWidth = 2 thisChart.BorderSkin.SkinStyle = System.Windows.Forms.DataVisualization.Charting.BorderSkinStyle.Emboss With thisChart.ChartAreas(0) .AxisX.MajorGrid.LineColor = Color.DimGray .AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Dash .AxisX.LabelStyle.ForeColor = Color.AntiqueWhite .AxisX.Minimum = 0 .AxisX.Title = "Time (ms)" .AxisX.TitleForeColor = Color.White .AxisY.MajorGrid.LineColor = Color.DimGray .AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Dash .AxisY.LabelStyle.Enabled = False .AxisY.IsMarginVisible = False .AxisX.ScrollBar.Enabled = True .AxisX.ScaleView.ZoomReset() .CursorX.IsUserEnabled = True .CursorX.IsUserSelectionEnabled = True .AxisX.ScaleView.Zoomable = True .BackColor = Color.Black End With End Sub Private Sub DrawChart() If ByteArray IsNot Nothing AndAlso ByteArray.Length > 0 Then Dim x, y, thismax As Integer Dim max As Integer = 400000 Chart1.Series.Clear() Chart1.Series.Add("Channel 1") Chart1.Series(0).ChartType = SeriesChartType.Line Chart1.Series(0).Color = Color.LimeGreen Chart2.Series.Clear() Chart2.Series.Add("Channel 2") Chart2.Series(0).ChartType = SeriesChartType.Line Chart2.Series(0).Color = Color.LimeGreen thismax = ByteArray.Length - 2 If thismax > max Then thismax = max Select Case NumChannels Case 1 'mono Select Case BitsPerSample Case 16 For i As Integer = 44 To thismax Step 2 Chart1.Series(0).Points.AddXY(x, (256 * ByteArray(i)) + ByteArray(i + 1) - 32512) x += 1 Next Case 8 For i As Integer = 44 To thismax Chart1.Series(0).Points.AddXY(i, ByteArray(i) - 127) Next Case Else MessageBox.Show(BitsPerSample.ToString & " Bits Per Sample Mono is not supported.") End Select Case 2 'stereo Select Case BitsPerSample Case 16 For i As Integer = 44 To thismax Step 4 'left channel y = BitConverter.ToInt16(CType(ByteArray, Byte()), i) Chart1.Series("Channel 1").Points.AddXY(x, y) 'right channel y = BitConverter.ToInt16(CType(ByteArray, Byte()), i + 2) Chart2.Series("Channel 2").Points.AddXY(x, y) x += 1 Next Case Else MessageBox.Show(BitsPerSample.ToString & " Bits Per Sample Stereo is not supported.") End Select End Select End If End Sub Private Function ByteDataAsHexString(rawBytes As IEnumerable(Of Byte)) As String Dim rawWaveDataHex As New Text.StringBuilder For i As Integer = 0 To rawBytes.Count - 1 If i > 0 Then If i Mod 24 = 0 Then rawWaveDataHex.AppendLine() Else rawWaveDataHex.Append(" ") End If End If rawWaveDataHex.Append(rawBytes(i).ToString("X2")) Next Return rawWaveDataHex.ToString End Function Private Function ByteDataAsWaveHeader(rawBytes As IEnumerable(Of Byte)) As String Dim waveHeader As New Text.StringBuilder waveHeader.Append("Chunk Descriptor: ") 'four bytes (0-3), character array waveHeader.Append(New String((From b In rawBytes.Take(4) Select Chr(b)).ToArray)) waveHeader.AppendLine() waveHeader.Append("Chunk Size: ") 'four bytes (4-7), int32 waveHeader.Append(BitConverter.ToInt32(CType(rawBytes, Byte()), 4)) waveHeader.AppendLine() waveHeader.Append("Format Identifier: ") 'four bytes (8-11), character array waveHeader.Append(New String((From b In rawBytes.Skip(8).Take(4) Select Chr(b)).ToArray)) waveHeader.AppendLine() waveHeader.Append("Subchunk Format: ") 'four bytes (12-15), character array waveHeader.Append(New String((From b In rawBytes.Skip(12).Take(4) Select Chr(b)).ToArray)) waveHeader.AppendLine() waveHeader.Append("Subchunk 1 Size: ") 'four bytes (16-19), int32 waveHeader.Append(BitConverter.ToInt32(CType(rawBytes, Byte()), 16)) waveHeader.AppendLine() waveHeader.Append("Audio Format: ") 'two bytes (20,21), int16 waveHeader.Append(BitConverter.ToInt16(CType(rawBytes, Byte()), 20)) waveHeader.AppendLine() waveHeader.Append("Channel Count: ") 'two bytes (22,23), int16 waveHeader.Append(BitConverter.ToInt16(CType(rawBytes, Byte()), 22)) waveHeader.AppendLine() waveHeader.Append("Sample Rate: ") 'four bytes (24-27), int32 waveHeader.Append(BitConverter.ToInt32(CType(rawBytes, Byte()), 24)) waveHeader.AppendLine() waveHeader.Append("Byte Rate: ") 'four bytes (28-31), int32 waveHeader.Append(BitConverter.ToInt32(CType(rawBytes, Byte()), 28)) waveHeader.AppendLine() waveHeader.Append("Block Align: ") 'two bytes (32,33), int16 waveHeader.Append(BitConverter.ToInt16(CType(rawBytes, Byte()), 32)) waveHeader.AppendLine() waveHeader.Append("Bits per Sample: ") 'two bytes (34,35), int16 waveHeader.Append(BitConverter.ToInt16(CType(rawBytes, Byte()), 34)) waveHeader.AppendLine() waveHeader.Append("Data Identifier: ") 'four bytes (36-39), character array waveHeader.Append(New String((From b In rawBytes.Skip(36).Take(4) Select Chr(b)).ToArray)) waveHeader.AppendLine() waveHeader.Append("Subchunk 2 Size: ") 'four bytes (40-43), int32 waveHeader.Append(BitConverter.ToInt32(CType(rawBytes, Byte()), 40)) waveHeader.AppendLine() 'End of header; sample data begins at byte number 44 Return waveHeader.ToString End Function End Class


    That is it for this discussion as I think its complicated enough.


    PS Thanks to all that helped !!!


    Wednesday, February 28, 2018 1:31 PM
  • Looks like you got it sorted out!  Those are the cleanest looking waveforms yet. :)

    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Wednesday, February 28, 2018 2:03 PM
    Moderator
  • Looks like you got it sorted out!  Those are the cleanest looking waveforms yet. :)

    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"


    Thanks Reed.

    I kept reading what you all said  but could not add the two stereo byte values correctly manually so I used the bitconverter and that was that. Just one line of code was wrong more or less. What will they think of next!

    :)

    Wednesday, February 28, 2018 2:32 PM
  •  Yes,  that looks much better Tom.  I will probably still mess with my project i started for doing this with 8bit, 16bit, 24bit, and 32bit mono/stereo files but,  it might not be for a while.  I will post it if I get back around to it soon.  You know how easily I get distracted with other interesting subjects.  8)

    If you say it can`t be done then i`ll try it

    Wednesday, February 28, 2018 8:22 PM
  •  Yes,  that looks much better Tom.  I will probably still mess with my project i started for doing this with 8bit, 16bit, 24bit, and 32bit mono/stereo files but,  it might not be for a while.  I will post it if I get back around to it soon.  You know how easily I get distracted with other interesting subjects.  8)

    If you say it can`t be done then i`ll try it


    Thanks Razerz.

    Update: I tried drawing the current playback position on the chart, using a timer and mediaplayer?.position and then invalidate the chart like at 100 ms but the chart redraw times are much too slow for like a 1 sec length wav it appears.

    So just for general info if anyone wanted to do interactive real-time drawing I think the chart graph has to be predrawn and then just draw on top of that bitmap. I think it ends up dump the chart control and draw your own it would be so involved.

    Maybe a routine to reduce the data points for drawing purposes would help.

    PS

    After additonal optimising I found I was wrong the chart control kicks behind on drawing your own see this new continuation.

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/ccff6fd9-3b36-4aa2-a9d4-ba3a56bae88f/how-to-draw-a-wav-chart-with-real-time-postion-indicator?forum=vbgeneral

    Wednesday, February 28, 2018 8:38 PM
  • ... I think it ends up dump the chart control and draw your own it would be so involved. ....

     I think that would be the best way to go if you wanted to be able to draw the position fast enough to keep up with the wave data being played.  I think the chart control has way to much overhead in it's drawing routine that you can't control.  I have not gotten even close to that point yet in my experimenting though.  I have been too busy with -tE's playing 24bit audio question.   8)

    If you say it can`t be done then i`ll try it

    Wednesday, February 28, 2018 11:57 PM