Click here to Skip to main content
15,895,142 members
Articles / Programming Languages / Visual Basic

MultiWave - a portable multi-device .NET audio player

Rate me:
Please Sign up or sign in to vote.
4.93/5 (23 votes)
13 May 2015Ms-PL11 min read 48.7K   6.6K   33  
This article describes the creation of a fully managed multi-device audio player using several public C# libraries. Requirements: VB .NET, Visual Studio 2008, .NET Framework 3.5
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Drawing
Imports System.Text
Imports System.Windows.Forms
Imports System.Drawing.Drawing2D

''' <summary>
''' Windows Forms control for painting stereo audio waveforms
''' </summary>
Public Class StereoWaveformPainter
    Inherits Panel

    Private foColor As Color
    Private grColor As Color
    Private GfxQuality As SmoothingMode = SmoothingMode.Default
    Private ComposingMode As CompositingMode = CompositingMode.SourceOver
    Private ComposingQuality As CompositingQuality = CompositingQuality.Default
    Private InterpolMode As InterpolationMode = InterpolationMode.Default
    Private PixelMode As PixelOffsetMode = PixelOffsetMode.Default
    Private DrMode As DisplayMode
    Private PlMode As Plotmode

    Private maxSamples As Integer
    Private insertPos As Integer

    Private LeftSamples As New List(Of Single)()
    Private RightSamples As New List(Of Single)()

    Private UseGradient As Boolean = False
    Private ShowMiddleLine As Boolean = False
    Private _pinch As PinchMode = PinchMode.None

    ''' <summary>
    ''' Constructs a new instance of the StereoWaveformPainter class
    ''' </summary>
    Public Sub New()
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer, True)
        OnForeColorChanged(EventArgs.Empty)
        OnResize(EventArgs.Empty)
    End Sub

    ''' <summary>
    ''' On ForeColor Changed
    ''' </summary>
    ''' <param name="e"></param>
    Protected Overrides Sub OnForeColorChanged(ByVal e As EventArgs)
        foColor = Me.ForeColor
        MyBase.OnForeColorChanged(e)
    End Sub

    ''' <summary>
    ''' On Resize
    ''' </summary>
    Protected Overrides Sub OnResize(ByVal e As EventArgs)
        maxSamples = Me.Width
        LeftSamples = New List(Of Single)(maxSamples)
        RightSamples = New List(Of Single)(maxSamples)
        insertPos = 0
        MyBase.OnResize(e)
    End Sub

    ''' <summary>
    ''' Add Left and Right channel values
    ''' </summary>
    ''' <param name="LeftSample"></param>
    Public Sub AddLeftRight(ByVal LeftSample As Single, ByVal RightSample As Single)

        If maxSamples = 0 Then
            ' sometimes when you minimise, max samples can be set to 0
            Return
        End If

        If LeftSamples.Count <= maxSamples Then
            If PlMode = Plotmode.LeftUpRightDown Then
                LeftSamples.Add(Math.Abs(LeftSample))
            ElseIf PlMode = Plotmode.PlusMinus Or PlMode = Plotmode.Dual Then
                LeftSamples.Add(LeftSample)
            End If
        ElseIf insertPos < maxSamples Then
            If PlMode = Plotmode.LeftUpRightDown Then
                LeftSamples(insertPos) = Math.Abs(LeftSample)
            ElseIf PlMode = Plotmode.PlusMinus Or PlMode = Plotmode.Dual Then
                LeftSamples(insertPos) = LeftSample
            End If
        End If

        If RightSamples.Count <= maxSamples Then
            If PlMode = Plotmode.LeftUpRightDown Then
                RightSamples.Add(Math.Abs(RightSample))
            ElseIf PlMode = Plotmode.PlusMinus Or PlMode = Plotmode.Dual Then
                RightSamples.Add(RightSample)
            End If
        ElseIf insertPos < maxSamples Then
            If PlMode = Plotmode.LeftUpRightDown Then
                RightSamples(insertPos) = Math.Abs(RightSample)
            ElseIf PlMode = Plotmode.PlusMinus Or PlMode = Plotmode.Dual Then
                RightSamples(insertPos) = RightSample
            End If
        End If

        insertPos += 1
        insertPos = insertPos Mod maxSamples

        Me.Invalidate()
    End Sub

    ''' <summary>
    ''' On Paint
    ''' </summary>
    Protected Overrides Sub OnPaint(ByVal pe As PaintEventArgs)
        MyBase.OnPaint(pe)

        'Apply gfx credentials.
        With pe.Graphics
            .SmoothingMode = GfxQuality
            .CompositingMode = ComposingMode
            .CompositingQuality = ComposingQuality
            .InterpolationMode = InterpolMode
            .PixelOffsetMode = PixelMode
        End With

        Dim middle As Single = Math.Floor(Me.Height / 2) - 1

        If PlMode = Plotmode.LeftUpRightDown Then
            If DrMode = DisplayMode.Line AndAlso GfxQuality = SmoothingMode.HighSpeed Then
                If ShowMiddleLine Then pe.Graphics.DrawLine(New Pen(foColor), 0, middle, Me.Width, middle)
                For x = 0 To Me.Width - 1
                    Dim leftLineHeight As Single = middle * GetLeftSample(x - Me.Width + insertPos)
                    Dim rightLineHeight As Single = middle * GetRightSample(x - Me.Width + insertPos)
                    ApplyPinch(leftLineHeight, x)
                    ApplyPinch(rightLineHeight, x)
                    pe.Graphics.DrawLine(New Pen(foColor), x, middle, x, middle - leftLineHeight)
                    pe.Graphics.DrawLine(New Pen(foColor), x, middle, x, middle + rightLineHeight)
                Next
            ElseIf DrMode = DisplayMode.Line AndAlso (GfxQuality = SmoothingMode.HighQuality OrElse GfxQuality = SmoothingMode.AntiAlias) Then
                Dim paLeft As New GraphicsPath
                Dim paRight As New GraphicsPath
                If ShowMiddleLine Then pe.Graphics.DrawLine(New Pen(foColor), 0, middle, Me.Width, middle)
                paLeft.AddLine(0, middle, 0, middle)
                paRight.AddLine(0, middle, 0, middle)
                For x = 0 To Me.Width - 1
                    Dim leftLineHeight As Single = middle * GetLeftSample(x - Me.Width + insertPos)
                    Dim rightLineHeight As Single = middle * GetRightSample(x - Me.Width + insertPos)
                    ApplyPinch(leftLineHeight, x)
                    ApplyPinch(rightLineHeight, x)
                    paLeft.AddLine(x, middle - leftLineHeight, x, middle - leftLineHeight)
                    paRight.AddLine(x, middle + rightLineHeight, x, middle + rightLineHeight)
                Next
                paLeft.AddLine(Me.Width - 1, middle, Me.Width - 1, middle)
                paRight.AddLine(Me.Width - 1, middle, Me.Width - 1, middle)
                paLeft.CloseFigure()
                paRight.CloseFigure()
                paLeft.AddPath(paRight, False)
                'pe.Graphics.DrawPath(New Pen(foColor), paLeft)
                If UseGradient Then
                    Using br = New LinearGradientBrush(New Rectangle(0, 0, Me.Width, Me.Height), grColor, foColor, LinearGradientMode.Vertical)
                        ApplyBlend(br)
                        pe.Graphics.FillPath(br, paLeft)
                    End Using
                Else
                    pe.Graphics.FillPath(New SolidBrush(foColor), paLeft)
                End If
                paLeft.Dispose()
                paRight.Dispose()
            ElseIf DrMode = DisplayMode.Point Then
                Dim paLeft As New GraphicsPath
                Dim paRight As New GraphicsPath
                If ShowMiddleLine Then paLeft.AddLine(0, middle, 0, middle) : paRight.AddLine(0, middle, 0, middle)
                For x = 0 To Me.Width - 1
                    Dim leftLineHeight As Single = middle * GetLeftSample(x - Me.Width + insertPos)
                    Dim rightLineHeight As Single = middle * GetRightSample(x - Me.Width + insertPos)
                    ApplyPinch(leftLineHeight, x)
                    ApplyPinch(rightLineHeight, x)
                    paLeft.AddLine(x, middle - leftLineHeight, x, middle - leftLineHeight)
                    paRight.AddLine(x, middle + rightLineHeight, x, middle + rightLineHeight)
                Next
                If ShowMiddleLine Then
                    paLeft.AddLine(Me.Width - 1, middle, Me.Width - 1, middle)
                    paRight.AddLine(Me.Width - 1, middle, Me.Width - 1, middle)
                    paLeft.CloseFigure()
                    paRight.CloseFigure()
                End If
                pe.Graphics.DrawPath(New Pen(foColor), paLeft) : pe.Graphics.DrawPath(New Pen(foColor), paRight)
                paLeft.Dispose()
                paRight.Dispose()
            End If
        ElseIf PlMode = Plotmode.PlusMinus Then
            If ShowMiddleLine Then pe.Graphics.DrawLine(New Pen(foColor), 0, middle, Me.Width, middle)
            Using path As New GraphicsPath()
                For x = 0 To Me.Width - 1
                    Dim Mixed As Single = (GetLeftSample(x - Me.Width + insertPos) + GetRightSample(x - Me.Width + insertPos)) / 2
                    ApplyPinch(Mixed, x)
                    If DrMode = DisplayMode.Line Then
                        path.AddLines(New Point() {New Point(x, middle), New Point(x, CInt(middle - middle * Mixed))})
                    ElseIf DrMode = DisplayMode.Point Then
                        path.AddLines(New Point() {New Point(x, CInt(middle - middle * Mixed)), New Point(x, CInt(middle - middle * Mixed))})
                    End If
                Next
                If DrMode = DisplayMode.Line Then
                    If UseGradient Then
                        Using br = New LinearGradientBrush(New Rectangle(0, 0, Me.Width, Me.Height), grColor, foColor, LinearGradientMode.Vertical)
                            ApplyBlend(br)
                            pe.Graphics.DrawPath(New Pen(br), path)
                        End Using
                    Else
                        pe.Graphics.DrawPath(New Pen(foColor), path)
                    End If
                ElseIf DrMode = DisplayMode.Point Then
                    pe.Graphics.DrawPath(New Pen(foColor), path)
                End If
            End Using
        ElseIf PlMode = Plotmode.Dual Then
            'Draw seperator line.
            If ShowMiddleLine Then pe.Graphics.DrawLine(New Pen(foColor), 0, middle, Me.Width, middle)
            'Left channel.
            Using leftpath As New GraphicsPath()
                For x = 0 To Me.Width - 1
                    Dim Mixed As Single = GetLeftSample(x - Me.Width + insertPos)
                    ApplyPinch(Mixed, x)
                    If DrMode = DisplayMode.Line Then
                        leftpath.AddLines(New Point() {New Point(x, CInt(middle / 2)), New Point(x, CInt((middle / 2) - (middle / 2 * Mixed)))})
                    ElseIf DrMode = DisplayMode.Point Then
                        leftpath.AddLines(New Point() {New Point(x, CInt(middle / 2 - (middle / 2 * Mixed))), New Point(x, CInt(middle / 2 - (middle / 2 * Mixed)))})
                    End If
                Next
                If DrMode = DisplayMode.Line Then
                    If UseGradient Then
                        Using br = New LinearGradientBrush(New Rectangle(0, 0, Me.Width, CInt(middle)), grColor, foColor, LinearGradientMode.Vertical)
                            ApplyBlend(br)
                            pe.Graphics.DrawPath(New Pen(br), leftpath)
                        End Using
                    Else
                        pe.Graphics.DrawPath(New Pen(foColor), leftpath)
                    End If
                ElseIf DrMode = DisplayMode.Point Then
                    pe.Graphics.DrawPath(New Pen(foColor), leftpath)
                End If
            End Using
            Using rightpath As New GraphicsPath()
                For x = 0 To Me.Width - 1
                    Dim Mixed As Single = GetRightSample(x - Me.Width + insertPos)
                    ApplyPinch(Mixed, x)
                    If DrMode = DisplayMode.Line Then
                        rightpath.AddLines(New Point() {New Point(x, CInt(Me.Height * 3 / 4)), New Point(x, CInt((Me.Height * 3 / 4) - (middle / 2 * Mixed)))})
                    ElseIf DrMode = DisplayMode.Point Then
                        rightpath.AddLines(New Point() {New Point(x, CInt(Me.Height * 3 / 4 - (middle / 2 * Mixed))), New Point(x, CInt(Me.Height * 3 / 4 - (middle / 2 * Mixed)))})
                    End If
                Next
                If DrMode = DisplayMode.Line Then
                    If UseGradient Then
                        Using br = New LinearGradientBrush(New Rectangle(0, 0, Me.Width, CInt(middle)), grColor, foColor, LinearGradientMode.Vertical)
                            ApplyBlend(br)
                            pe.Graphics.DrawPath(New Pen(br), rightpath)
                        End Using
                    Else
                        pe.Graphics.DrawPath(New Pen(foColor), rightpath)
                    End If
                ElseIf DrMode = DisplayMode.Point Then
                    pe.Graphics.DrawPath(New Pen(foColor), rightpath)
                End If
            End Using
        End If
    End Sub

    ''' <summary>
    ''' Applies the x position pinch either to raw or mixed samples.
    ''' </summary>
    ''' <param name="RecieveSample">The recieve sample.</param>
    ''' <param name="x">The x pos.</param>
    Private Sub ApplyPinch(ByRef RecieveSample As Single, ByRef x As Integer)
        Select Case _pinch
            Case PinchMode.None
                Exit Select
            Case PinchMode.Outside_Sine
                RecieveSample *= Math.Sin(x / Me.Width * Math.PI)
            Case PinchMode.Inside_Cosine
                RecieveSample *= Math.Cos(x / Me.Width * Math.PI)
            Case PinchMode.Inverse_MDCT
                RecieveSample *= Math.Sin(0.5 * Math.PI * (Math.Sin((x + 0.5) / Me.Width * Math.PI) ^ 2))
        End Select
    End Sub

    ''' <summary>
    ''' Applies a blend effect to a LinearGradientBrush.
    ''' </summary>
    ''' <param name="Brush">The LinearGradientBrush that will recieve the blend.</param>
    Private Sub ApplyBlend(ByRef Brush As LinearGradientBrush)
        Dim relativeIntensities As Single() = {0.0F, 1.0F, 0.0F}
        Dim relativePositions As Single() = {0.0F, 0.5F, 1.0F}
        Dim blend As New Blend()
        blend.Factors = relativeIntensities
        blend.Positions = relativePositions
        Brush.Blend = blend
    End Sub

    Private Function GetLeftSample(ByVal index As Integer) As Single
        If index < 0 Then
            index += maxSamples
        End If
        If index >= 0 And index < LeftSamples.Count Then
            Return LeftSamples(index)
        End If
        Return 0
    End Function

    Private Function GetRightSample(ByVal index As Integer) As Single
        If index < 0 Then
            index += maxSamples
        End If
        If index >= 0 And index < RightSamples.Count Then
            Return RightSamples(index)
        End If
        Return 0
    End Function

    ''' <summary>
    ''' Gets or sets the quality of all drawn graphics.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the quality of all drawn graphics.")> _
    Public Property QualityMode() As SmoothingMode
        Get
            Return GfxQuality
        End Get
        Set(ByVal value As SmoothingMode)
            GfxQuality = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the compositing mode of all drawn graphics.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the compositing mode of all drawn graphics.")> _
    Public Property CompositingMode() As CompositingMode
        Get
            Return ComposingMode
        End Get
        Set(ByVal value As CompositingMode)
            ComposingMode = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the compositing quality of all drawn graphics.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the compositing quality of all drawn graphics.")> _
    Public Property CompositingQuality() As CompositingQuality
        Get
            Return ComposingQuality
        End Get
        Set(ByVal value As CompositingQuality)
            ComposingQuality = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the interpolation mode of all drawn graphics.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the interpolation mode of all drawn graphics.")> _
    Public Property InterpolationMode() As InterpolationMode
        Get
            Return InterpolMode
        End Get
        Set(ByVal value As InterpolationMode)
            InterpolMode = value
        End Set
    End Property


    ''' <summary>
    ''' Gets or sets the pixel offset mode of all drawn graphics.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the pixel offset mode of all drawn graphics.")> _
    Public Property PixelOffsetMode() As PixelOffsetMode
        Get
            Return PixelMode
        End Get
        Set(ByVal value As PixelOffsetMode)
            PixelMode = value
        End Set
    End Property

    ''' <summary>
    ''' Enumerates the display modes
    ''' </summary>
    Public Enum DisplayMode
        Line = 0
        Point = 1
    End Enum

    ''' <summary>
    ''' Enumerates the pinch modes
    ''' </summary>
    Public Enum PinchMode
        None = 0
        Outside_Sine = 1
        Inside_Cosine = 2
        Inverse_MDCT = 3
    End Enum

    ''' <summary>
    ''' Enumerates the plotting modes
    ''' </summary>
    Public Enum Plotmode
        PlusMinus = 0
        LeftUpRightDown = 1
        Dual = 2
    End Enum

    ''' <summary>
    ''' Gets or sets the plotting method.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the plotting method.")> _
    Public Property Plot() As Plotmode
        Get
            Return PlMode
        End Get
        Set(ByVal value As Plotmode)
            PlMode = value
            'Reset.
            LeftSamples.Clear()
            RightSamples.Clear()
            insertPos = 0
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the display method.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the display method.")> _
    Public Property DrawMode() As DisplayMode
        Get
            Return DrMode
        End Get
        Set(ByVal value As DisplayMode)
            DrMode = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets if the display uses gradient colors.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets if the display uses gradient colors.")> _
    Public Property Gradient() As Boolean
        Get
            Return UseGradient
        End Get
        Set(ByVal value As Boolean)
            UseGradient = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the gradient color, that is used in Line Mode.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the gradient color, that is used in Line Mode.")> _
    Public Property GradientColor() As Color
        Get
            Return grColor
        End Get
        Set(ByVal value As Color)
            grColor = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets if the middle line is displayed.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets if the middle line is displayed (Line HQ Mode must show).")> _
    Public Property ShowMidLine() As Boolean
        Get
            Return ShowMiddleLine
        End Get
        Set(ByVal value As Boolean)
            ShowMiddleLine = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the way pinch is applied to the waveform.
    ''' </summary>
    <System.ComponentModel.Description("Gets or sets the way pinch is applied to the waveform.")> _
    Public Property Pinch() As PinchMode
        Get
            Return _pinch
        End Get
        Set(ByVal value As PinchMode)
            _pinch = value
        End Set
    End Property
End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Engineer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions