B4J Question How to Draw Waveforms of WAV Files

xulihang

Active Member
Licensed User
Longtime User
I need to draw wave forms of audio files. It has to be scalable horizontally and vertically.

Using @stevel05 's WavRandomAccessFile, I can get the raw data and visualize it using a java plot library:

B4X:
Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
    wavRaf.Initialize(File.DirApp,"segment-00006.wav")
    Dim data() As Double = wavRaf.DataDoubles
    Dim jo As JavaObject = Me
    jo.RunMethod("plot",Array(data))
End Sub

#if java
import com.sin.java.plot.Plot;
import com.sin.java.plot.PlotFrame;

public static void plot(double[] data) {
    PlotFrame frame = Plot.figrue("WaveForm");
    frame.setSize(500, 200);
    Plot.hold_on();
    Plot.plot(data);
    Plot.hold_off();
}
#End If

1735991282287.png



I prefer to use a B4X implementation and xGraph by @klaus seems a good option. But I cannot figure out how to use it. Any direction would be appreciated.
 
Last edited:

emexes

Expert
Licensed User
Lol I was about to say that @klaus has some pretty good plotting action going on (understatement of the new year) but you've already found it.

It might be overkill feature-wise for what you need. On the other hand, it might struggle with the potentially millions of points too.

This post refers to a WavDraw class, which sounds like it is in the ballpark of what you're ooking to do:

 
Upvote 0

klaus

Expert
Licensed User
Longtime User
How long are your wav files.

Attached an attempt to do the job, it works with B4J and B4A.
I had to remove the wav files because they are to big.
But, i think that the xGraph library is not the best way to do it because of the big number of points.
The display takes quite some time.
A more dedicated display would be more efficient.
And you would probably also zoom in and out.

1736006225011.png
 

Attachments

  • DisplayWave.zip
    27.6 KB · Views: 13
Upvote 0

emexes

Expert
Licensed User
If just the audio envelope (like your birds-eye view) is enough, then you could reduce down the sample rate to say 20 chunks per seconds eg min and max of 2205 cd audio samples. I'm sure @klaus charts wouldn't blink at 4800 chunks of a 4-minute song.
 
Upvote 0

xulihang

Active Member
Licensed User
Longtime User
I only need the birds-eye view waveform of a 30-second file so I can compress the samples from like 124928 to 5000 with the following code:

B4X:
Private Sub Compress(data() As Double) As Double()
    Dim times As Int = data.Length / 5000
    Dim compressed(5000) As Double
    For i = 0 To compressed.Length - 1
        compressed(i) = data(i*times)
    Next
    Return compressed
End Sub

The speed is fairly okay.

1736044559788.png
 
Upvote 0

emexes

Expert
Licensed User
compress the samples

Beautiful.

Two minor observations:

1/ consider:
B4X:
Dim times As Float = (data.Length - 1) / (5000 - 1)
mainly because let's say data.Length was 19000, then integer times = 3, which multiplied by highest i value 4999 only gets you to data sample 14997 ie less than 80% of way through the 19000 samples

2/ using Double for audio feels like overkill, unless you're doing 24-bit audio, but here if the "compressed" data is only used for screen display then that's only 11 bits of resolution on a UHD display, well within the 23 bits of resolution of Float. CD is 16 bits of resolution which is also no problem for a Float, even if it the extraction DSP manages to add a couple of bits of resolution.
 
Upvote 0

xulihang

Active Member
Licensed User
Longtime User
I decided to use bitmap creator to create a waveform bitmap. There are other things I haven't implemented, like scaling and mapping the coordinates.


B4X:
Sub Class_Globals
    Private fx As JFX
    Private xui As XUI
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
    
End Sub

Public Sub Draw(data() As Short) As B4XBitmap
    data = Compress(data)
    Dim normalized() As Float= Normalize(data)
    Dim bc As BitmapCreator
    bc.Initialize(3000,500)
    Dim height As Int = 500 - 200
    Dim NbSamples As Int = normalized.Length
    For i = 0 To NbSamples - 1
        If i = NbSamples - 1 Then
            Exit
        End If
        Dim centerY As Double = 250
        Dim x1 As Int = i
        Dim y1 As Double = centerY + normalized(i)*height
        Dim x2 As Int = i+1
        Dim y2 As Double = centerY + normalized(i+1)*height
        bc.DrawLine(x1,y1,x2,y2,xui.Color_Red,1)
    Next
    Return bc.Bitmap
End Sub

Private Sub Compress(data() As Short) As Short()
    Dim times As Int
    If data.Length > 3000 Then
        times = data.Length / 3000
    Else
        Return data
    End If
    Dim compressed(3000) As Short
    For i = 0 To compressed.Length - 1
        compressed(i) = data(i*times)
    Next
    Return compressed
End Sub

Private Sub Normalize(data() As Short) As Float()
    Dim normalized(data.Length) As Float
    Dim maxValue As Short = 0
    For i = 0 To data.Length - 1
        Dim value As Short = data(i)
        maxValue = Max(maxValue,value)
    Next
    For i = 0 To data.Length - 1
        normalized(i) = data(i) / maxValue
    Next
    Return normalized
End Sub
 
Upvote 0
Top