none
How to draw a WAV chart graph with real time postion indicator.

    General discussion

  • Hi all,

    This is a continuation of this thread where we covered the wav chart basics:

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/8266cc7c-a544-494b-911d-e599bafc8008/how-to-draw-a-wav-sound-file-graph-and-get-the-header-data?forum=vbgeneral


    I did some further testing and found that the chart control is much faster than drawing your own wav graph. I drew the wav graph with gdi+ on a bufferedgraphic and found the chart to be much faster.

    So I further optimized the chart part by only plotting every 40th point in the byte array. This can vary to as much as /100 and you dont see much difference in the blondie rapture.wav. I also used the .fastline chart instead of .line for more speed.


    The blondi rapture wav file is available here for a limited time.


    Now I am trying to figure what the time increments are for the x axis.

    You can see in the example that I have added a timer and each timer tick I get the playback position in seconds from media player and convert that to the actual chart coordinates. I found this code works ok for the blondi wav which is 16 bit stereo (mono not working):

        Dim t As Double = 1.25 * NumChannels * BitsPerSample * Player.controls.currentItem.duration

    Notice the 1.25 factor I needed to get the correct ratio to convert with. However I think with proper use of the header info the factor should not be needed. It seems the equation should be a simple ratio of something but I am not sure what?

    Does anyone know how to calc what the actual time step is for each value in the wav bytearray?

    Furthermore, the time scale shown is off x200 the actual time ie the blondi wav is about 8.4 secs so the x axis scale should be 8000 millisecs at the end not 40000. I need to make that correction somewhere.

    Finally, I am using mediaplayer in this example v1 but maybe there is a better way? Like Media.SoundPlayer or mcisendstring?

    Here is the example code for v1. The example makes all the controls all you have to do is cut and paster to an empty form.

    The moving red line in the animation below is the play position when you play the wav. That's what I am talking about (runs smoother in real life).

    PS I just realized part of the problem in the time scale in the example is I don't consider I am only plotting every 40th point. So need a x40 somewhere I guess.

    Edit: v2 has different time function as described below.

    'chart wav sound file with play postion v2
    'lmb drag the chart area to zoom
    Imports System.IO
    Imports System.Windows.Forms.DataVisualization.Charting
    Public Class Form6
        Private cBackClr As Color = Color.Black
        Private cForeClr As Color = Color.AntiqueWhite
        Private cBorder As Integer = 10
    
        Private WithEvents Player As WMPLib.WindowsMediaPlayer
        Private WithEvents Timer1 As New Timer With {.Interval = 10}
        Private WithEvents OpenBtn As New Button With {.Parent = Me, .Text = "Open",
                .Location = New Point(20, cBorder), .ForeColor = cForeClr, .BackColor = cBackClr}
        Private WithEvents PlayBtn As New Button With {.Parent = Me, .Text = "Play",
                .Location = New Point(100, cBorder), .ForeColor = cForeClr, .BackColor = cBackClr}
        Private WithEvents ZoomResetBtn As New Button With {.Parent = Me, .Text = "Zoom Out",
                .Location = New Point(180, cBorder), .ForeColor = cForeClr, .BackColor = cBackClr}
        Private WithEvents PathLabel As New Label With {.Parent = Me, .Font = New Font("tahoma", 10),
                .Location = New Point(270, cBorder), .ForeColor = cForeClr, .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 SoundFilePath As String
        Private ByteArray() As Byte
        Private NumChannels As Integer
        Private BytesPerSample As Integer
        Private SampleRate As Double
        Private CurrentPosition As Double
    
        Private Sub Form5_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ClientSize = New Size(500, 400)
            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
            Chart1.Width = ClientSize.Width - (2 * cBorder)
            Chart2.Width = Chart1.Width
        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 PlayFile(ByVal url As String)
            Player = New WMPLib.WindowsMediaPlayer
            Player.URL = url
            Player.controls.play()
        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
                PlayFile(SoundFilePath)
                Timer1.Start()
            End If
        End Sub
    
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            If Player.controls.currentItem.duration > 0 Then
                Dim t As Double = 1.25 * NumChannels * BytesPerSample * Player.controls.currentItem.duration
                Dim r As Double = (ByteArray.Length - 144) / t
    
                If Player.controls.currentPosition + 0.1 > Player.controls.currentItem.duration Then
                    Timer1.Stop()
                    CurrentPosition = GetIndexFromTimePoint(
                        Player.controls.currentItem.duration, SampleRate, BytesPerSample)
                Else
                    CurrentPosition = GetIndexFromTimePoint(
                        Player.controls.currentPosition, SampleRate, BytesPerSample)
                End If
    
                Chart1.Invalidate()
                Chart2.Invalidate()
            End If
    
        End Sub
    
        'Private Function GetTimePointFromIndex(index As Integer, thisSampleRate As Integer, thisBitsPerSample As Short, thisChannelCount As Short) As TimeSpan
        '    Dim secondsPerSample As Double = 1 / thisSampleRate
        '    Dim sampleBypeCount As Integer = (thisBitsPerSample \ 8) * thisChannelCount
        '    index = CInt(Math.Floor(index / sampleBypeCount) * sampleBypeCount)
        '    Return TimeSpan.FromSeconds((sampleBypeCount * index) * secondsPerSample)
        'End Function
    
        Private Function GetIndexFromTimePoint(elapsedTime As Double, thisSampleRate As Double, thisBytesPerSample As Integer) As Integer
            Dim i As Integer = -1
    
            i = CInt(((256 / 10) * elapsedTime * thisSampleRate) / (thisBytesPerSample * thisBytesPerSample))
    
            Return i
        End Function
    
        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
    
                ByteArray = File.ReadAllBytes(SoundFilePath)
    
                NumChannels = BitConverter.ToInt16(CType(ByteArray, Byte()), 22)
                BytesPerSample = BitConverter.ToInt16(CType(ByteArray, Byte()), 34)
                SampleRate = BitConverter.ToInt32(CType(ByteArray, Byte()), 24)
    
                If NumChannels = 1 Then Chart2.Visible = False Else Chart2.Visible = True
    
                PathLabel.Text = SoundFilePath & vbCrLf &
                                "Channels: " & NumChannels &
                                "  Bits: " & BytesPerSample &
                                "  Sample Rate: " & SampleRate
                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 = cForeClr
                .AxisX.Minimum = 0
                .AxisX.Title = "Time (ms)"
                .AxisX.TitleForeColor = cForeClr
    
                .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 = cBackClr
            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.FastLine
                Chart1.Series(0).Color = Color.LimeGreen
                Chart2.Series.Clear()
                Chart2.Series.Add("Channel 2")
                Chart2.Series(0).ChartType = SeriesChartType.FastLine
                Chart2.Series(0).Color = Color.LimeGreen
    
                thismax = ByteArray.Length - 2
                '            If thismax > max Then thismax = max
    
                Select Case NumChannels
                    Case 1   'mono
                        Select Case BytesPerSample
                            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 'Step 10
                                    Chart1.Series(0).Points.AddXY(i, ByteArray(i) - 127)
                                Next
                            Case Else
                                MessageBox.Show(BytesPerSample.ToString & " Bits Per Sample Mono is not supported.")
                        End Select
    
                    Case 2  'stereo
                        Select Case BytesPerSample
                            Case 16
                                For i As Integer = 44 To thismax Step 40
                                    '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(BytesPerSample.ToString & " Bits Per Sample Stereo is not supported.")
                        End Select
                End Select
            End If
        End Sub
    
        Private Sub Chart1_PostPaint(sender As Object, e As ChartPaintEventArgs) Handles Chart1.PostPaint
            DrawPostion(e.ChartGraphics, Chart1)
        End Sub
    
        Private Sub Chart2_PostPaint(sender As Object, e As ChartPaintEventArgs) Handles Chart2.PostPaint
            DrawPostion(e.ChartGraphics, Chart2)
        End Sub
    
        Private Sub DrawPostion(g As ChartGraphics, thisChart As Chart)
            If ByteArray IsNot Nothing AndAlso ByteArray.Length > 0 Then
                With g
                    'get the  coords in chart coords
                    Dim x As Single = CSng(.GetPositionFromAxis(thisChart.ChartAreas(0).Name, AxisName.X, CurrentPosition))
                    Dim y As Single = CSng(.GetPositionFromAxis(thisChart.ChartAreas(0).Name, AxisName.Y, 0))
    
                    'convert to model
                    Dim point1 As PointF = New PointF(x, y)
                    point1 = .GetAbsolutePoint(point1)
    
                    x = CSng(.GetPositionFromAxis(thisChart.ChartAreas(0).Name, AxisName.X, CurrentPosition))
    
                    Dim point2 As PointF = New PointF(x, 2 * y)
                    point2 = .GetAbsolutePoint(point2)
    
                    .Graphics.DrawLine(Pens.Red, point1.X, point2.Y, point1.X, -point2.Y)
                End With
            End If
        End Sub
    
    End Class
    

    Friday, March 2, 2018 6:52 PM

All replies

  • PS I just realized part of the problem in the time scale in the example is I don't consider I am only plotting every 40th point. So need a x40 somewhere I guess.


    PS Can anyone figure how to change the white area of the chart to a different color?
    Friday, March 2, 2018 7:05 PM
  • Hi Tommy,

    I believe the formula for getting a time point from a data byte index should be:

    Private Function GetTimePointFromIndex(index As Integer, sampleRate As Integer, bitsPerSample As Short, channelCount As Short) As TimeSpan
        Dim secondsPerSample As Double = 1 / sampleRate
        Dim sampleBypeCount As Integer = (bitsPerSample \ 8) * channelCount
        index = Math.Floor(index / sampleBypeCount) * sampleBypeCount
        Return TimeSpan.FromSeconds((sampleBypeCount * index) * secondsPerSample)
    End Function

    The time step per sample is just the sample rate - 48,000 samples per second means 1 sample is 1/48000 of a second.

    The number of bytes per sample is the bit depth divided by 8 (8 bits per byte), multiplied by the number of channels.

    The given index into the wave data byte array must be reduced to the closest index which represents the beginning of a sample, to allow any arbitrary index value.

    Finally the playback time at that point in the data stream is calculated as bytes-per-sample multiplied by the index multiplied by the time-per-sample.

    Does that give you the info you need?


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


    Friday, March 2, 2018 7:15 PM
    Moderator
  • Thanks Reed,

    I used your function and show some result below. Seems good.

    What I need for the graph is to go the other direction of course.

    Anyhow the mediaplayer gives the current play position as elapsed time in secs double so I went with that. I figured it here. Of course in the end I still have an unknow factor now its 256!

    Here is my homework. Reed said:

    "The time step per sample is just the sample rate - 48,000 samples per second means 1 sample is 1/48000 of a second."

    So sample rate is 1 / 48000 secs = 1 / 48 ms = .02083 ms

    What is a sample?

    In the 16 bit stereo we add two 8 bit bytes from the bytearray for left and two for right, so is a sample 4 bytes from the bytesarray which is for array items ie
     bytearray(0), bytearray(1), bytearray(2), bytearray(3) is one sample?


    "Finally the playback time at that point in the data stream is calculated as bytes-per-sample multiplied by the index multiplied by the time-per-sample."

    so time/sample = 1/48000 secs. = 1 / 48 ms = sample rate (sr).

    bytes-per-sample (bps) = 4

    and say for the second sample of four bytes the index would be ((2-1) * 4)  = 4 where -1 for zero based array. So elapsed time (et) by 4 byte index:

    index bytearray(n)     et

    1       0 1 2 3             0.33
    2       4 5 6 7            0.66
    3       8 9 10 11        1.00
    4       12 13 14 15    1.33
    5       16 17 18 19    1.66
    6       20 21 22 23    2.00
    7       24 25 26 27    2.33
    8       28 29 30 31    2.66
    9       32 33 34 35    3.00


    Here is Reed's GetTimePointFromIndex function results rounding to ms:

        bytearray(n)    millisecs

               0-7            0
               8-19           1
               20-31         2


    so for the 2 index (i) in the bit array the elapsed play time (et) would be:

          et = bps *  (i * 4) / 48

    where 4 is the bytes per sample again.

          et = 4 * (2 * 4) / 48 = .667 ms

    for the 3 and 4 index

          et = 4 * (3 * 4) / 48 = 1.0 ms

          et = 4 * (4 * 4) / 48 = 1.33 ms


    so each sample is dt = 0.33 ms long or for one sample (4 bytes in the bytearray):

          dt = 4 * (1 * 4) / 48 = 0.33 ms

          dt = bps * bps / sr

    What the example needs is a function to go the other direction. We know what the elapsed time is during playback by using mediaplayer.controls.currentPosition. Now we want the index (i) in the byte array. That is the chart x axis value where we will draw the red play position line.

    So where et is the elapsed play position time from media player:

          et = bps *  (i * 4) / sr


           i * 4  = et * sr / bps

           i  = et * sr / (bps * 4)

    so what is 4 ? bps again I guess?

           i =  (et * sr) /(bps * bps)

    check: say the time is 1.33 then we get

           i =  (1.33333 * 48) /(4 * 4) = 4


    But alas as you can see I had to use what I did in GetIndexFromTimePoint       

                i = CInt(((256 / 10) * elapsedTime * thisSampleRate) /
                              (thisBytesPerSample * thisBytesPerSample))

    where the 10 is my reduction factor in the drawchart routine. And I dont know where 256 is comming from.

    So I revised the example to v2 with this function.

    :)

    PS this is all using the blondi 16 bit stereo, mono position is incorrect.




    Saturday, March 3, 2018 2:57 AM
  • Hi Tommy,

    You've got a sample defined correctly.  For 16bit, 2-channel a sample is 4 bytes.

    The inverse of the function I posted previously would be:

    Private Function GetIndexFromTimePoint(seconds As Double, sampleRate As Integer, bitsPerSample As Short, channelCount As Short) As Integer
        Dim secondsPerSample As Double = 1 / sampleRate
        Dim sampleBypeCount As Integer = (bitsPerSample \ 8) * channelCount
        Return CInt(Math.Floor(seconds / secondsPerSample / sampleBypeCount))
    End Function
    

    I think the main issue with the code you've been using is that this line is mislabeled:

    BytesPerSample = BitConverter.ToInt16(CType(ByteArray, Byte()), 34)
    That should be BitsPerSample.  That number divided by 8 is the bytes per sample for one channel.  So the total bytes per sample is BitsPerSample / 8 * ChannelCount.


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

    Saturday, March 3, 2018 6:22 PM
    Moderator
  • Thanks for the inverse function Reed.

    Yes I had the byte bit wrong there. I divided by 8. But there is still some factor being missed when the position is drawn. I half see the problem but cant explain it. We are plotting a count to the chart...??? ...dont really want the array index, sorry, its the time we need...

    Anyhow, I smartened up and now give the xaxis point a conversion to milliseconds and now the chart points are in milliseconds not counts. So the chart is in the correct time scale. Now I can just draw the position in milliseconds which comes from mediaplayer.

    I updated the example to v3 which seems to show the time and playback position well.

    Here is v3 of the example with the change. Everything is working now more or less? Except one funny thing is it wont draw the pacmaceaten.wav but monstereaten is ok. So must be a problem there somewhere.


    'chart wav sound file with play postion v4
    'lmb drag the chart area to zoom
    Imports System.IO
    Imports System.Windows.Forms.DataVisualization.Charting
    Public Class WavChartPosition
        Private cBackClr As Color = Color.Black
        Private cForeClr As Color = Color.AntiqueWhite
        Private cBorder As Integer = 10
        Private WithEvents Player As WMPLib.WindowsMediaPlayer
        Private WithEvents Timer1 As New Timer With {.Interval = 10}
        Private WithEvents OpenBtn As New Button With {.Parent = Me, .Text = "Open",
                .Location = New Point(20, 2 * cBorder), .ForeColor = cForeClr, .BackColor = cBackClr}
        Private WithEvents PlayBtn As New Button With {.Parent = Me, .Text = "Play",
                .Location = New Point(100, OpenBtn.Top), .ForeColor = cForeClr, .BackColor = cBackClr}
        Private WithEvents ZoomResetBtn As New Button With {.Parent = Me, .Text = "Zoom Out",
                .Location = New Point(180, OpenBtn.Top), .ForeColor = cForeClr, .BackColor = cBackClr}
        Private WithEvents PathLabel As New Label With {.Parent = Me, .Font = New Font("tahoma", 10),
                .Location = New Point(270, OpenBtn.Top), .ForeColor = cForeClr, .BackColor = Color.Transparent, .AutoSize = True}
        Private WithEvents Chart1 As New DataVisualization.Charting.Chart With {.Parent = Me,
                .Location = New Point(cBorder, OpenBtn.Bottom + 2 * cBorder), .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 SoundFilePath As String
        Private ByteArray() As Byte
        Private NumChannels, BitsPerSample As Short
        Private SampleRate As Integer
        Private CurrentPosition As Double
        Private PointReduction As Integer = 20
    
        Private Sub Form5_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ClientSize = New Size(600, 400)
            BackColor = Color.DimGray
            Text = "Play Wav"
            ChartAreasinitialize(Chart1, "Left")
            ChartAreasinitialize(Chart2, "Right")
        End Sub
    
        Private Sub Form5_Resize(sender As Object, e As EventArgs) Handles Me.Resize
            Chart1.Width = ClientSize.Width - (2 * cBorder)
            Chart2.Width = Chart1.Width
        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
                    'read the wav file and make the header data
                    Cursor = Cursors.WaitCursor
                    SoundFilePath = d.FileName
                    ByteArray = File.ReadAllBytes(SoundFilePath)
                    'get the header info items we use
                    NumChannels = BitConverter.ToInt16(CType(ByteArray, Byte()), 22)
                    BitsPerSample = BitConverter.ToInt16(CType(ByteArray, Byte()), 34)
                    SampleRate = BitConverter.ToInt32(CType(ByteArray, Byte()), 24)
    
                    'for mono hide right channel chart
                    If NumChannels = 1 Then Chart2.Visible = False Else Chart2.Visible = True
    
                    PathLabel.Text = SoundFilePath & vbCrLf &
                                "Channels: " & NumChannels &
                                "  Bits: " & BitsPerSample &
                                "  Sample Rate: " & SampleRate
                    ChartInitialize()
    
                    PlayBtn.Text = "Play"
                    Form5_Resize(0, Nothing)
                    Cursor = Cursors.Default
                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
                If PlayBtn.Text = "Play" Then
                    Timer1.Start()
                    PlayBtn.Text = "Pause"
                    Player = New WMPLib.WindowsMediaPlayer
                    Player.URL = SoundFilePath
                    Player.controls.play()
                Else
                    PlayBtn.Text = "Play"
                    Player.controls.pause()
                    Timer1.Stop()
                End If
            End If
        End Sub
    
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            If Player.controls.currentItem.duration > 0 Then
                Select Case BitsPerSample
                    Case 8
                        CurrentPosition = PointReduction * 1000 * Player.controls.currentPosition
                    Case 16
                        CurrentPosition = 1000 * Player.controls.currentPosition
                End Select
    
                If Player.controls.currentPosition + 0.05 > Player.controls.currentItem.duration Then
                    Timer1.Stop()
                    PlayBtn.Text = "Play"
                End If
    
                Chart1.Invalidate()
                Chart2.Invalidate()
            End If
        End Sub
    
        Private Sub ZoomResetBtn_Click(sender As Object, e As EventArgs) Handles ZoomResetBtn.Click
            Chart1.ChartAreas(0).AxisX.ScaleView.ZoomReset()
            Chart2.ChartAreas(0).AxisX.ScaleView.ZoomReset()
        End Sub
    
        Private Sub ChartAreasinitialize(thisChart As Chart, thisName As String)
            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
    
            thisChart.ChartAreas.Add(thisName)
    
            With thisChart.ChartAreas(0)
                .AxisX.MajorGrid.LineColor = Color.DimGray
                .AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Dash
                .AxisX.LabelStyle.ForeColor = cForeClr
                .AxisX.Minimum = 0
                .AxisX.Title = "Time (ms)"
                .AxisX.TitleForeColor = cForeClr
                .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 = cBackClr
            End With
        End Sub
    
        Private Sub ChartSeriesInitialize(thischart As Chart, chartname As String)
            With thischart
                .Series.Clear()
                .Series.Add(chartname)
                .Series(0).ChartType = SeriesChartType.FastLine
                .Series(0).Color = Color.LimeGreen
            End With
        End Sub
    
        Private Sub ChartInitialize()
            If ByteArray IsNot Nothing AndAlso ByteArray.Length > 0 Then
                Dim x, y, max, x2 As Integer
                max = ByteArray.Length - 2       'If max > 4000000 Then max = 4000000
    
                ChartSeriesInitialize(Chart1, "Channel 1")
                ChartSeriesInitialize(Chart2, "Channel 2")
    
                Select Case NumChannels
                    Case 1   'mono
                        Select Case BitsPerSample
                            Case 8
                                For i As Integer = 44 To max
                                    x2 = CInt((1000 * x / SampleRate))
                                    Chart1.Series(0).Points.AddXY(x2, 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 max Step 4 * PointReduction
                                    'left channel 
                                    x2 = CInt((PointReduction * 1000 * x / SampleRate))
                                    y = BitConverter.ToInt16(CType(ByteArray, Byte()), i)
                                    Chart1.Series("Channel 1").Points.AddXY(x2, y)
    
                                    'right channel 
                                    y = BitConverter.ToInt16(CType(ByteArray, Byte()), i + 2)
                                    Chart2.Series("Channel 2").Points.AddXY(x2, 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 Sub Chart1_PostPaint(sender As Object, e As ChartPaintEventArgs) Handles Chart1.PostPaint
            DrawPostion(e.ChartGraphics, Chart1)
        End Sub
    
        Private Sub Chart2_PostPaint(sender As Object, e As ChartPaintEventArgs) Handles Chart2.PostPaint
            DrawPostion(e.ChartGraphics, Chart2)
        End Sub
    
        Private Sub DrawPostion(g As ChartGraphics, thisChart As Chart)
            If ByteArray IsNot Nothing AndAlso ByteArray.Length > 0 Then
                With g
                    'get the  coords in chart coords
                    Dim x As Single = CSng(.GetPositionFromAxis(thisChart.ChartAreas(0).Name, AxisName.X, CurrentPosition))
                    Dim y As Single = CSng(.GetPositionFromAxis(thisChart.ChartAreas(0).Name, AxisName.Y, 0))
                    'convert to model
                    Dim point1 As PointF = New PointF(x, y)
                    point1 = .GetAbsolutePoint(point1)
                    x = CSng(.GetPositionFromAxis(thisChart.ChartAreas(0).Name, AxisName.X, CurrentPosition))
                    Dim point2 As PointF = New PointF(x, 2 * y)
                    point2 = .GetAbsolutePoint(point2)
                    .Graphics.DrawLine(Pens.Red, point1.X, point2.Y, point1.X, -point2.Y)
                End With
            End If
        End Sub
    End Class
    






    • Edited by tommytwotrain Sunday, March 4, 2018 11:46 PM v4 optimized
    Saturday, March 3, 2018 10:52 PM
  •  Looks like you are getting it worked out one step at a time.  It is looking good Tom.  8)

     I have not forgot about this yet.  I got my (WaveFileData) class from my example in your last post fixed up today so that it will open 8, 16, 24, and 32 bit mono or stereo pcm wave files and convert their wave data values to floating point values (-1.0 to 1.0 where 0.0 is silence).  I figured this would be the easiest way to make it so that no mater what bit depth they are,  the chart's Y values need no modifications or scaling.  Only the X axis will need to be adjusted.

      I just started working on using a Chart to display the data a little while ago and it is coming along good so far.  I just need to get the timeline (milliseconds) on the X axis fixed to display correct.  Hopefully I will get some time to work on it more tomorrow.  When I get it figured out I will post it here,  or just a link to it on OneDrive.  I have been busy today shoveling/plowing the 2 feet of snow we had yesterday.  Winter could stop now and I would not complain at all.  8)

     Here is a 5 second 16bit,  44100hz file shown in Audacity (top) and in my form (bottom).  Looks about right so,  as I said,  I just need to get the Timeline figured out.  Should not be to hard I don't think.

     The chart seems to be pretty sluggish with this much data in it so,  I think after getting it done the next step might be trying to make my own control to display the data.  I am getting out of here for tonight so,  talk at ya later.  8)


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

    Sunday, March 4, 2018 2:44 AM
  • Razerz,

    Yeah looks good!

    " I just need to get the timeline (milliseconds) on the X axis fixed to display correct. "

    Oh thats easy! :)

     x2 = CInt((PointReduction * 1000 * x / SampleRate))

    Where x is the point count.
                                 
    "The chart seems to be pretty sluggish with this much data in it so, "

    Just use every 20th point (pointreduction). And .fastline chart type. I guess with the higher rates 24 and 32 maybe more like use every 200 and 2000th point? Or some other scheme. And then for best it should vary based on zoom level ie 6 secs shown or 2 secs shown horizontally.

    Yeah the vertical y axis units are sort of pointless. I just let the chart do it auto scale based on the max min of the plotted points.

    Do you have a play mode? What are you using to play the file? I am not sure what PCM is I guess I have to read up on it.

    Well, I guess to draw much larger files one is looking at directx/directsound somehow. I have a sample you did I may look at it.

    Ooooh... that's an interesting thought, a 3d wav chart.

    However I was quite surprized the chart control was maybe x10 times faster than I could draw them with gdi+.

    I was realizing last night that the animation timeline editor I made would work perfectly for a sound file editor time line. BTW I got the Audacity and that is what I mean by timeline editor if it was not clear. So you can cut clips from one sound file and add them together and move and adjust multiple sounds together...

    In fact I am imagining a new file format GifMuse an animated gif with music.

    Sunday, March 4, 2018 8:51 AM
  • Tom,

     "x2 = CInt((PointReduction * 1000 * x / SampleRate))"

     I will have to look back through your code to see what, where, and how you are using that line.  I made a project and tested your first version in this post yesterday and I don't know if I saw that in there or not.  I know I have to calculate the sample rate in there but,  I was so wore out yesterday that I just did not get that far.  It won't take me too long to get that fixed up though,  I think.

     

     "Just use every 20th point (pointreduction). And .fastline chart type. I guess with the higher rates 24 and 32 maybe more like use every 200 and 2000th point?  Or some other scheme. And then for best it should vary based on zoom level ie 6 secs shown or 2 secs shown horizontally."

     You would have to intercept the selection in the chart so that you could reload the new set of Data Points for the selected data each time it is scaled it up,  otherwise skipping samples for example,  in a sine wave tone would look severely distorted.  I did play with skipping every x number of samples yesterday,  and it did change the look of it.  As you said,  it depends on how long the timeline of the file is and the sample rate.

     

     "Yeah the vertical y axis units are sort of pointless. I just let the chart do it auto scale based on the max min of the plotted points."

     The Y axis is to show the amplitude of the sound.  If you loaded a file that had 100% amplitude,  it would look identical to one that had 50% amplitude.  You can see in my image in my last post how the amplitude is only about 50% (-0.5 to 0.5),  it does not fill the chart height.  This is why I scaled the data from any file to a floating point value (-1.0 to 1.0),  to accurately show the true amplitude.

     

     "Do you have a play mode? What are you using to play the file? I am not sure what PCM is I guess I have to read up on it."

     I did not get that far yesterday but,  I was thinking I might go with the waveOutxxx apis such as I used in the "Wave Player Example" that I posted a link to in -tE's thread the other day,  or maybe even the mciSendString apis.

     PCM stands for Pulse-code modulation.

     

     "However I was quite surprized the chart control was maybe x10 times faster than I could draw them with gdi+."

     Huh,  I would not have guessed that.  I might still try it though when I get that far.

     

     I was looking into how to programmatically mix wave file data last week which would be handy for creating an audio editor that could be something like Audacity where you can load 2 or more files, view, cut, paste, copy, and then mix them into a new file if wanted.  I don't quite have the full grasp on doing the mixing yet though.

     

     "In fact I am imagining a new file format GifMuse an animated gif with music."

     Maybe a CadGif Audio Editor is in the works here?


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

    Sunday, March 4, 2018 2:01 PM
  • Razerz,

    Well what we need is just an array(s) of data. In the chart example I last posted v3 the array we want is the series that is made for the chart. With stereo there is a left and right series.

    So you should be able to plug your data in that just like the bytearray in the example and use the example code unchanged. See my comment on the v3 example.

    Doh. WHERE the HECK is the code block button????????????? Its MIA from the forum editor ????????

                       For i As Integer = 44 To thismax Step 4 * PointReduction
                                    'left channel
                                    x2 = CInt((PointReduction * 1000 * x / SampleRate))
                                    y = BitConverter.ToInt16(CType(ByteArray, Byte()), i)
                                    Chart1.Series("Channel 1").Points.AddXY(x2, y)

    So where it says bytearray it would say RazerzArray.

    I mean that is the idea. So just use example v3 if you like.

    Yeah, for mixing parts of wavs together then we need Reeds functions where you can select a section of a wav in the wav graph from the time data in the chart. Then we need the index in the data array. But to draw the position line, all we need is the current playback time in the wav file as it plays in media player or whatever.

    Yeah in the previous discussion I had just used the bytearray as a stream in

        Private Sub PlayBtn_Click(sender As Object, e As EventArgs) Handles PlayBtn.Click
            'play the current wav
            Using MSWav As IO.MemoryStream = New IO.MemoryStream(ByteArray),
            SP As New Media.SoundPlayer(MSWav)
                MSWav.Position = 0
                SP.Play()
            End Using

        End Sub

    which I thought was good if one could get the position from media.soundplayer but I did not see an easy way. So if your "Wave Player Example" has that then...

    Just depends on how much interest one has in it all.

    :)

    PS

    So it leads to several arrays I suppose. One with all points from the original wav file made when the wave is opened. Then one for the wav in its current view size. So for like 10 secs thats reduce by 100 or whatever. Then if the user zooms in make a new array from the org wav arrray reduced x20 when the user zooms.

    And then of course for play back if zoomed the time line needs to move to each zoom section of the total wav...

    So, what I suggest is we make one example for all to use??? If the example v3 does what you want. Maybe not. Or maybe you prefer to do one yourself that is fine too it will prob be better...

    PS and then I smell a wav data class being born...

    PS " I was looking into how to programmatically mix wave file data last week which would be handy for creating an audio editor"

    Seems that is just a simple replacement of the org array values. ie say you select index 2222 thru 4321 that's a clip, and then you paste that into the master array values. At the selection point in that array index 164860... 

    And you can either insert the clip or paste over.

    But with a time line editor you do that with two graphs, one is wav 1 and one is wav 2 ie you combine monstereaten into blondi for a new wav monsterblondi mix. In the editor you see two time lines, one for each wav. The you can gui to cut them up and move them, when one time line is over another they get added (or inserted)...

    In the time line there are two gifs. You can move them. You can drag the ends to clip them... then slide the clip copy paste etc...

    Here I moved clip 2 over clip 1:

    so when 1 and 2 are over each other both play. That's where the fancy code would come in. For a gif that would be like a fade transition or whatever.

    So imagine instead of a gif in the image there is a wav graph.

    And for gifmuse there is both and many time lines stacked up.

    But all that is another discussion...


    Sunday, March 4, 2018 2:57 PM
  •  Tom,

     I see where that line is in your code now.  I will still have to look it over a bit (pun intended).  I got tied up doing some more in my conversion method for getting floating point values for the data.  Seems I was not figuring for the IEEE_FLOAT format data differences.  Which leads me to the question...

     Do you or anyone else reading this know right off hand if the IEEE_FLOAT format tag for floating point data values is/can be used for 4bit through 24bit data,  or is it only used for 32, 48, and 64 bit data?

     "Just depends on how much interest one has in it all."

     I started working on a wave file editor a while back which did not make it far before I got distracted with other things.  It is kind of interesting to me like animated gif files are.  I suspect my interest in this stems from a mix between my interest in many things like playing guitar, working on audio amplifiers, programming, and how files are structured and stored on disk.  Plus more....


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

    • Edited by IronRazerz Sunday, March 4, 2018 4:39 PM
    Sunday, March 4, 2018 4:38 PM
  •  Just when you thought this discussion was long gone into the past....

     Just wanted to mention that I did not forget about this and have been working a little on my own control to use along with my WaveFileData class.  I still have a little more to do on the 'tick marks' for the timeline so that I am sure they work properly in all scenarios but,  below is what it looks like.  I only show a few of the zoom steps 1, 2, 4, and 10 to keep the gif small in size but,  you can zoom it to whatever number you want.

     

     Now I need to add a Scroll method so you can scroll when it is zoomed to 2 or more.  Then the Selection method for selecting parts of the wave data.  It seems to be just as fast or maybe a little faster,  drawing wise,  as using the chart was.  I'll be back sooner or later....  8)


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

    Saturday, March 10, 2018 11:32 PM
  • Looks great Razerz!

    Very professional.

    Sunday, March 11, 2018 1:38 AM