I was working on an alternative way using a min/max array to draw but it seems you probably won't need it.
I will post it anyway; maybe someone else can use it.
The idea was to create an array of min max values so the drawing of the wave would be faster when you want to update the waveform while playing audio.
But maybe the previous approach was already fast enough for that. I didn't try that.
Code: Select all
Procedure.q MinMaxScaled(*MinMaxArray, SamplesPerElement, *SampleData, SampleCount,
Scale = 48, Stereo = #True, Cont.q=$800080007fff7fff)
; <<< MinMaxScaled >>>
; This mmx optimized procedure fills an array with scaled min/max values.
; A scale of 64 means the output values will be in range [-64, 63].
; Max scale is 128 so each value fit in a byte [-128, 127].
Protected.i reg_b
If (*MinMaxArray And SamplesPerElement > 0) And (*SampleData And SampleCount > 0)
If Scale > 128 : Scale = 128 : EndIf
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov [p.v_reg_b], rbx ; backup rbx
!mov rax, [p.p_SampleData]
!mov rdx, [p.p_MinMaxArray]
CompilerElse
!mov [p.v_reg_b], ebx ; backup ebx
!mov eax, [p.p_SampleData]
!mov edx, [p.p_MinMaxArray]
CompilerEndIf
!mov ecx, [p.v_SamplesPerElement]
!movq mm0, [p.v_Cont] ; load continuation value
!pcmpeqw mm3, mm3 ; create inversion mask
!psrlq mm3, 32
!mov ebx, [p.v_Scale] ; load scale value
!imul ebx, 0x00020002
!movd mm4, ebx
!punpckldq mm4, mm4
!.l0:
!cmp ecx, [p.v_SampleCount] ; compare SamplesPerElement with SampleCount
!jbe .l1
!mov ecx, [p.v_SampleCount]
!.l1:
!cmp dword [p.v_Stereo], 0 ; stereo / mono check
!jnz .l2
; load mono sample
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movzx ebx, word [rax]
!add rax, 2
CompilerElse
!movzx ebx, word [eax]
!add eax, 2
CompilerEndIf
!movd mm1, ebx
!punpcklwd mm1, mm1
!jmp .l3
!.l2:
; load stereo sample
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd mm1, [rax]
!add rax, 4
CompilerElse
!movd mm1, [eax]
!add eax, 4
CompilerEndIf
!.l3:
!punpckldq mm1, mm1 ; duplicate sample into l-r-l-r
!movq mm2, mm0
!pcmpgtw mm2, mm1 ; compare min/max with new sample
!pxor mm2, mm3
!pand mm0, mm2 ; update min/max
!pandn mm2, mm1
!por mm0, mm2
!sub ecx, 1
!jnz .l1
!pmulhw mm0, mm4 ; scale min/max values
!packsswb mm0, mm0 ; convert min/max from word to byte
; store scaled min/max values
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd [rdx], mm0
!add rdx, 4
CompilerElse
!movd [edx], mm0
!add edx, 4
CompilerEndIf
!movq mm0, mm1
!mov ecx, [p.v_SamplesPerElement]
!sub [p.v_SampleCount], ecx ; decrease SampleCount
!ja .l0
!movq [p.v_Cont], mm1 ; store continuation value
!emms
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov rbx, [p.v_reg_b] ; restore rbx
CompilerElse
!mov ebx, [p.v_reg_b] ; restore ebx
CompilerEndIf
EndIf
ProcedureReturn Cont
EndProcedure
Structure MinMax
min.b[2]
max.b[2]
EndStructure
Procedure UpdateCanvas(Canvas, Array MM.MinMax(1), Offset = 0)
Protected.i Width, Height, VOffsetL, VOffsetR, MaxX, X, X_
StartDrawing(CanvasOutput(Canvas))
Width = OutputWidth() : Height = OutputHeight()
VOffsetL = Height >> 2 : VOffsetR = VOffsetL + Height >> 1
Box(0, 0, Width, Height, $ffe0e0e0)
Line(0, VOffsetL, Width, 1, $ffa0a0a0)
Line(0, VOffsetR, Width, 1, $ffa0a0a0)
X_ = Offset : MaxX = ArraySize(MM())
While X < Width And X <= MaxX
LineXY(X, VOffsetL - MM(X_)\min[0], X, VOffsetL - MM(X_)\max[0], $ffff8000)
LineXY(X, VOffsetR - MM(X_)\min[1], X, VOffsetR - MM(X_)\max[1], $ffff8000)
X + 1 : X_ + 1
Wend
StopDrawing()
EndProcedure
SamplesPerElement = 64
BASS_Init(-1, 44100, 0, 0, #Null)
Channel.l = BASS_StreamCreateFile(#False, @"Test.mp3", 0, 0, #BASS_STREAM_PRESCAN|#BASS_STREAM_DECODE|#BASS_UNICODE)
Length.q = BASS_ChannelGetLength(Channel, #BASS_POS_BYTE)
NumSamples.q = Length >> 2
*WaveData = AllocateMemory(Length)
BASS_ChannelGetData(Channel, *WaveData, Length)
Dim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 52)
OpenWindow(0, 0, 0, 620, 360, "Waveform", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 10, 10, 600, 300)
ButtonGadget(1, 20, 320, 40, 20, "<<")
ButtonGadget(2, 70, 320, 40, 20, ">>")
ButtonGadget(3, 120, 320, 40, 20, "+")
ButtonGadget(4, 170, 320, 40, 20, "-")
UpdateCanvas(0, MM())
Offset = 0
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Gadget
Select EventGadget()
Case 1
Offset - 600
If Offset < 0 : Offset = 0 : EndIf
Case 2
Offset + 600
Case 3
If SamplesPerElement > 1
SamplesPerElement >> 1 : Offset << 1
EndIf
ReDim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 52)
Case 4
If SamplesPerElement < 65536
SamplesPerElement << 1 : Offset >> 1
EndIf
ReDim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 52)
EndSelect
UpdateCanvas(0, MM(), Offset)
EndIf
Until Event = #PB_Event_CloseWindow
Code: Select all
Procedure.l MinMaxScaled(*MinMaxArray, SamplesPerElement, *SampleData, SampleCount,
Scale = 48, Stereo = #True, Cont.l=0)
; <<< MinMaxScaled >>>
; This sse2 optimized procedure fills an array with scaled min/max values.
; A scale of 64 means the output values will be in range [-64, 63].
; Max scale is 128 so each value fit in a byte [-128, 127].
If (*MinMaxArray And SamplesPerElement > 0) And (*SampleData And SampleCount > 0)
If Scale > 128 : Scale = 128 : EndIf
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov [rsp-8], rbx ; backup rbx
!movdqu [rsp-24], xmm6 ; backup xmm6
!movdqu [rsp-40], xmm7 ; backup xmm7
!mov rax, [p.p_SampleData]
!mov rdx, [p.p_MinMaxArray]
CompilerElse
!mov [esp-4], ebx ; backup ebx
!mov eax, [p.p_SampleData]
!mov edx, [p.p_MinMaxArray]
CompilerEndIf
!mov ecx, [p.v_SamplesPerElement]
!mov ebx, 1 ; set xmm6 to 1/SamplesPerElement
!cvtsi2sd xmm6, ebx
!cvtsi2sd xmm1, ecx
!divsd xmm6, xmm1
!movlhps xmm6, xmm6
!mov ebx, [p.v_Scale] ; set xmm7 to scale value
!imul ebx, 0x00020002
!movd xmm7, ebx
!pshufd xmm7, xmm7, 0
!movd xmm0, [p.v_Cont] ; load continuation value
!.l0:
!movdqa xmm2, xmm0
!movdqa xmm3, xmm0
!xorpd xmm4, xmm4
!xorpd xmm5, xmm5
!cmp ecx, [p.v_SampleCount] ; compare SamplesPerElement with SampleCount
!jbe .l1
!mov ebx, 1
!mov ecx, [p.v_SampleCount]
!cvtsi2sd xmm6, ebx
!cvtsi2sd xmm1, ecx
!divsd xmm6, xmm1
!movlhps xmm6, xmm6
!.l1:
!cmp dword [p.v_Stereo], 0 ; stereo / mono check
!jnz .l2
; load mono sample
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movzx ebx, word [rax]
!add rax, 2
CompilerElse
!movzx ebx, word [eax]
!add eax, 2
CompilerEndIf
!movd xmm0, ebx
!punpcklwd xmm0, xmm0
!jmp .l3
!.l2:
; load stereo sample
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd xmm0, [rax]
!add rax, 4
CompilerElse
!movd xmm0, [eax]
!add eax, 4
CompilerEndIf
!.l3:
!movdqa xmm1, xmm0
!punpcklwd xmm1, xmm1 ; convert word to long
!psrad xmm1, 16
!cvtdq2pd xmm1, xmm1 ; convert long to double
!pminsw xmm2, xmm0 ; update min
!pmaxsw xmm3, xmm0 ; update max
!addpd xmm4, xmm1 ; update sum
!mulpd xmm1, xmm1
!addpd xmm5, xmm1 ; update sumsq
!sub ecx, 1
!jnz .l1
!mulpd xmm4, xmm6 ; divide sum by samples per element
!mulpd xmm5, xmm6 ; divide sumsq by samples per element
!sqrtpd xmm5, xmm5 ; square root of sumsq
!cvtpd2dq xmm4, xmm4 ; convert to long
!cvtpd2dq xmm5, xmm5
!packssdw xmm4, xmm4 ; convert to word
!packssdw xmm5, xmm5
!punpckldq xmm2, xmm3
!punpckldq xmm4, xmm5
!punpcklqdq xmm2, xmm4 ; combine min/max/avg/rms
!pmulhw xmm2, xmm7 ; scale values
!packsswb xmm2, xmm2 ; convert from to byte
; store scaled values
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movq [rdx], xmm2
!add rdx, 8
CompilerElse
!movq [edx], xmm2
!add edx, 8
CompilerEndIf
!mov ecx, [p.v_SamplesPerElement]
!sub [p.v_SampleCount], ecx ; decrease SampleCount
!ja .l0
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movdqu xmm7, [rsp-40] ; restore xmm7
!movdqu xmm6, [rsp-24] ; restore xmm6
!mov rbx, [rsp-8] ; restore rbx
CompilerElse
!mov ebx, [esp-4] ; restore ebx
CompilerEndIf
EndIf
!movd eax, xmm0 ; return continuation value
ProcedureReturn
EndProcedure
Structure MinMax
min.b[2]
max.b[2]
avg.b[2]
rms.a[2]
EndStructure
Procedure UpdateCanvas(Canvas, Array MM.MinMax(1), Offset = 0, RMS = #False)
Protected.i Width, Height, VOffsetL, VOffsetR, MaxX, X, X_
StartDrawing(CanvasOutput(Canvas))
Width = OutputWidth() : Height = OutputHeight()
VOffsetL = Height >> 2 : VOffsetR = VOffsetL + Height >> 1
X_ = Offset : MaxX = ArraySize(MM())
Box(0, 0, Width, Height, $ffe0e0e0)
If RMS
Line(0, VOffsetL, Width, 1, $ffa0a0a0)
Line(0, VOffsetR, Width, 1, $ffa0a0a0)
While X < Width And X_ <= MaxX
LineXY(X, VOffsetL, X, VOffsetL - MM(X_)\rms[0], $ffc0c000)
LineXY(X, VoffsetR, X, VOffsetR - MM(X_)\rms[1], $ffc0c000)
X + 1 : X_ + 1
Wend
Else
Line(0, VOffsetL, Width, 1, $ffa0a0a0)
Line(0, VOffsetR, Width, 1, $ffa0a0a0)
While X < Width And X_ <= MaxX
LineXY(X, VOffsetL - MM(X_)\min[0], X, VOffsetL - MM(X_)\max[0], $ffffc000)
LineXY(X, VOffsetR - MM(X_)\min[1], X, VOffsetR - MM(X_)\max[1], $ffffc000)
Plot(X, VOffsetL - MM(X_)\avg[0], $ffff8000)
Plot(X, VOffsetR - MM(X_)\avg[1], $ffff8000)
X + 1 : X_ + 1
Wend
EndIf
StopDrawing()
EndProcedure
SamplesPerElement = 64
BASS_Init(-1, 44100, 0, 0, #Null)
Channel.l = BASS_StreamCreateFile(#False, @"Test.mp3", 0, 0, #BASS_STREAM_PRESCAN|#BASS_STREAM_DECODE|#BASS_UNICODE)
Length.q = BASS_ChannelGetLength(Channel, #BASS_POS_BYTE)
NumSamples.q = Length >> 2
*WaveData = AllocateMemory(Length)
BASS_ChannelGetData(Channel, *WaveData, Length)
Dim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 64)
OpenWindow(0, 0, 0, 620, 360, "Waveform", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 10, 10, 600, 300)
ButtonGadget(1, 20, 320, 40, 20, "<<")
ButtonGadget(2, 70, 320, 40, 20, ">>")
ButtonGadget(3, 120, 320, 40, 20, "+")
ButtonGadget(4, 170, 320, 40, 20, "-")
CheckBoxGadget(5, 250, 320, 60, 20, "RMS")
StringGadget(6, 500, 320, 100, 20, "0.000")
Offset = 0
UpdateCanvas(0, MM(), Offset, GetGadgetState(5))
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Gadget
Select EventGadget()
Case 1
Offset - 600
If Offset < 0 : Offset = 0 : EndIf
Case 2
Offset + 600
Case 3
If SamplesPerElement > 1
SamplesPerElement >> 1 : Offset << 1
EndIf
ReDim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 64)
Case 4
If SamplesPerElement < 65536
SamplesPerElement << 1 : Offset >> 1
EndIf
ReDim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 64)
Case 6
Offset = ValD(GetGadgetText(6))*44100/SamplesPerElement
UpdateCanvas(0, MM(), Offset, GetGadgetState(5))
Continue
EndSelect
UpdateCanvas(0, MM(), Offset, GetGadgetState(5))
SetGadgetText(6, StrD(Offset*SamplesPerElement/44100, 3))
EndIf
Until Event = #PB_Event_CloseWindow