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