plot audio data in gnuplot - audio

how could I convert an audio file such as a aiff into a svg using gnuplot? I used sox (sound exchange) to convert an .aiff into a .dat, which I can load now in gnuplot.
I did something similar to:
set terminal svg
set output "test.svg"
plot "test.dat"
I get a svg file, but only with dots / or a lot of x.
How could I connect the dots?

To draw lines between the points, use
plot "test.dat" with lines
Or to keep the point markers as well as the lines, use
plot "test.dat" with linespoints
So your example becomes
set terminal svg
set output "test.svg"
plot "test.dat" with lines
Further tips:
Don't consider every sample:
With large files you may also find it useful to plot only every nth sample with "every n". This will make the plot much faster to generate and will also yield a smaller (but less detailed) svg file.
e.g.
plot "test.dat" every 100 with lines
Ignore .dat file header:
If your sox-produced .dat file has some lines of introductory metadata, such as
; Sample Rate 44100
; Channels 2
you can add the following to have gnuplot consider those lines comments and ignore them.
set datafile commentschars ";"
This will save you having to pre-process your .dat file in order to remove those lines before gnuplot chokes on them.
Plot both left and right channels of stereo audio:
If you're working with a stereo file, you probably want to see both channels.
We can use "multiplot" to lay out the following two plots (of left then right channel) one above the other on a shared x-axis, as many sound-editing programs do.
set multiplot layout 2,1
plot "test.dat" using 1:2 with lines
plot "" using 1:3 with lines
The 1:2 and 1:3 instruct gnuplot which columns of the dat file to use as x and y sources. I'm assuming your stereo .dat file produced by sox looks as mine does, with columns for
- 1: time since beginning of first sample
- 2: normalized sample value of left channel
- 3: normalized sample value of right channel
example snippet:
10.840113 0.20101929 0.17840576
10.840136 0.26062012 0.14831543
10.840159 0.23779297 0.13146973
Putting it together:
Here's a script which puts all of the above together. If you don't have a stereo data file to try this with, you'll want to remove the plot of 1:3 and the multiplot setting.
#!/usr/bin/env gnuplot
set datafile commentschars ";"
set terminal svg
set output "test.svg"
set multiplot layout 2,1
plot "test.dat" using 1:2 every 100 with lines
plot "" using 1:3 every 100 with lines
unset multiplot
Prettification
Finally, I've tweaked the script for presentation (borrowing heavily from the excellent "gnuplot in action" book by Philipp K. Janert):
#!/usr/bin/env gnuplot
set datafile commentschars ";"
set terminal svg
set output "test.svg"
set multiplot layout 2,1
set ylabel "sample value"
set bmargin 0
set format x ""
set ytics -0.8,0.2
set key bottom
plot "test.dat" using 1:2 every 100 with lines lc rgbcolor "#a0a0b0" title "left channel"
set xlabel "time (s)"
set bmargin
set tmargin 0
set format x "%g"
set ytics -1.0,0.2,0.8
set key top
plot "" using 1:3 every 100 with lines lc rgbcolor "#a0a0b0" title "right channel"
unset multiplot
Here's an example output (albeit png):
How to make a .dat file
For anyone following along at home, you can use sox to generate a .dat file from an audio file with the following command:
sox input.wav output.dat
Big file warning: Converting even just 10 seconds of stereo audio at 40kHz will produce a 25Mb output file.

Note that you can also plot the binary data directly:
set terminal svg
set output "test.svg"
plot '< sox test.aiff -t s32 -' binary format='%int32' using 0:1 with lines

Just wanted to document this - well, I was looking for a long time for a Linux command line audio waveform viewer, which could be called from the command line, with a raw binary file as input, and where the format of the data could be specified on the command line.
Audacity can import raw data, but only from the GUI (there is no way to specify raw datafile format through its command line options); while wave viewers like gwave, gtkwave or Gaw - Gtk Analog Wave viewer can either read proper .wav, or SPICE based formats.
And thanks to the answer by #Thor, now I know I can use gnuplot for the purpose. Here is an example command line, which interprets the raw binary data as 16-bit stereo:
gnuplot -p -e "set terminal x11 ; set multiplot layout 2,1 ; plot 0 ls 2, 'data.raw' binary format='%int16%int16' using 0:1 with lines ls 1; plot 0 ls 2, 'data.raw' binary format='%int16%int16' using 0:2 with lines ls 1 ; unset multiplot"
... or broken in several lines:
gnuplot -p -e "set terminal x11 ; set multiplot layout 2,1 ; \
plot 0 ls 2, 'data.raw' binary format='%int16%int16' using 0:1 with lines ls 1; \
plot 0 ls 2, 'data.raw' binary format='%int16%int16' using 0:2 with lines ls 1; \
unset multiplot"
Note that:
you should only use pipe "< ..." if you want to output from a shell command - if you have a file (like above), don't use the pipe (else getting permission denied)
Note the format '%int16%int16' will cause the byte stream to be "grouped" as 2 bytes representing column (channel) 1, the next 2 bytes as column (channel) 2, the next 2 bytes again as column 1, and so on... see gnuplot docs_4.2: Binary General - Format (also related: Gnuplot: How to plot multiple time series from a binary format)
Finally with two independent plots, one using 0:1 and the other using 0:2, we can get a typical waveform rendering (as in accepted answer) - with one channel above the other
Since the --persist option is used above, gnuplot will exit, while the (x11 or wxt) window will remain - and so, the typical gnuplot interaction with the window will not work
Anyways, glad I found this, will save me quite a bit of time, I think :)

Related

FFMpeg Piped GNUPlot Audio Waveform, "Can't Change Waveform Color" (Windows)

I have spent a couple of days trying to change the default waveform color in the command line Gnuplot portion with no success. Here is the code that works perfectly it outputs a purple audio waveform on a transparent background. I want to be able to change the purple color to an RGB color (i.e. "#ff0000" format).
ffmpeg -y -i "Traveling Man.mp3" -ac 1 -filter:a aresample=8000 -map 0:a -c:a pcm_s16le -f data - | gnuplot -p -e "set terminal png size 1000,250; set output 'waveform.png'; unset key; set term png transparent truecolor; unset tics; unset border; set lmargin 0; set rmargin 0; set tmargin 0; set bmargin 0; plot '-' binary filetype=bin format='%int16' endian=little array=1:0 with lines;"
I have tried adding:
set linetype 9 lc rgb '0000ff'; set linetype cycle 9;
to the command line in various places and it either throws an error or outputs purple on transparent.
This has to be something very simple, I just can't seem to get it because the documentation does not show any usefull command-line examples. Any ideas are welcome, thank you.
You can change the color for each plot by explicitly specifying it. For the 'lines' style like in your example, you can specify the line color by adding "lc rgb '#ff0000'" after "with lines". The modified plot command will look like this.
plot '-' binary filetype=bin format='%int16' endian=little array=1:0 with lines lc rgb '#ff0000';
You can get more detailed control over the style of each plot by typing the following help command at the gnuplot prompt.
gnuplot> help plot with

GNUPlot: Auto-plot all columns from standard in without extra logic/semantic noise

Suppose I have the following file:
0 a b c
1 1 2 2
2 4 4 2
3 8 6 2
And I want to send it from the command line:
cat foofile | gnuplot -e 'file="/dev/stdin"' plot.p
With the following plotfile:
set key outside
plot for [col=2:4] file using 0:col with lines title columnheader
Now, suppose I would like to send GNUPlot an arbitrary columnar file with anywhere from 1:N columns, where N is arbitrary.
Since I am opening a pipe from within GNUPlot, it seems that I will not be able to read from it to determine the number of columns. How do I tell GNUPlot (in a clean way) to simply plot each column until there are none left to plot?
plot for [col=2:*] file using 0:col with lines title columnheader
However, if you know what the file name is in order to issue the "cat" command you would do better to just pass the filename to gnuplot rather than using a pipe.
gnuplot -e 'file="foofile"' plot.p
Alternatively, you can feed the input via channels other than /dev/stdin. Here is an excerpt from the documentation section for piped-data:
On systems with an fdopen() function, data can be read from an arbitrary file
descriptor attached to either a file or pipe. To read from file descriptor
`n` use `'<&n'`. This allows you to easily pipe in several data files in a
single call from a POSIX shell:
$ gnuplot -p -e "plot '<&3', '<&4'" 3<data-3 4<data-4
$ ./gnuplot 5< <(myprogram -with -options)
gnuplot> plot '<&5'

Gnuplot: Change Density of dotted line in splot

I am trying to plot a dotted line within an splot with the following code in Gnuplot 4.6 patchlevel 4:
set terminal "pdfcairo" enhanced dashed size 15,10
set pm3d map
set output "test.pdf"
splot 'map.dat' using 1:($2/1000):3 notitle, \
'line1.dat' using 1:($2/1000):1 notitle with lines ls 2, \
'line2.dat' using 1:($2/1000):1 notitle with lines ls 2
unset output
The heat map works and so does line1.dat. However, the second line appears mostly solid. The difference is that line1.dat has 70 entries and line2.dat has 900. The second line has a jump between two points and there it is dotted.
Does somebody know how I can change the dot density so that the whole line appears dotted. Changing the original data file is not an option.
Thank you for your help,
noes
EDIT:
One workaround I found is
splot 'line2.dat' every ...
but that can get unconvenient at the jump in the data.
The command (s)plot 'line.dat' with lines first plots the datapoints and then connects the datapoints using lines with the respective linestyle. If the datapoints are too close to each other, there is no place for some gaps when a dashed linestyle is used.
To display a dotted/dashed line, you can try to replace the points by a function or to reduce the number of points.
Try dotted lines instead of dashed lines. Linestyle and linecolor can be set independently: splot 'line.dat' with lines ls 0 lc 2. 900 points might be too many for this approach.
Fitting a function would work, but probably it is too difficult to find a suitable function.
The every option reduces the number of points.
Another possibility to reduce the number of points would be to interpolate the points using the smooth option. This requires a temporary file and works as follows:
# [prepare plot]
set samples 100
set table "line2.dat.tmp"
plot 'line2.dat' using 1:($2/1000) smooth mcsplines with lines ls 2
unset table
set terminal "pdfcairo" enhanced dashed size 15,10
set pm3d map
set output "test.pdf"
# [plot]
splot 'map.dat' using 1:($2/1000):3 notitle, \
'line1.dat' using 1:($2/1000):1 notitle with lines ls 2, \
'line2.dat.tmp' using 1:2:1 notitle with lines ls 2
unset output
In the [prepare plot] section a temporary file "line2.dat.tmp" is created which contains datapoints interpolating line2.dat. You have to play with set samples to get the right number of points. In the example we have 100 equidistant points instead of 900 points with different distances.
The option smooth mcsplines preserves the monotonicity and convexity of the original data points, see help smooth mcsplines in a gnuplot shell.
In the [plot] section the original "lines2.dat" is replaced by the interpolated data.
This approach works if the original data is smooth enough so that replacing 900 points by 100 points does not skip important information. Maybe you want to plot both "lines2.dat" and "lines2.dat.tmp" in a single diagram to compare them.
User the every key-word, like this:
'line2.dat' every 20 using 1:($2/1000):1 notitle with lines ls 2

Gnuplot - Using replot with png terminal

I am trying to use replot with png terminal in Gnuplot.
If I do the following I get two plots on one graph without any problem:
plot sin(x)/x
replot sin(x)
Now if do the same for a png terminal type the resulting png file only contains the first plot.
set terminal png
set output 'file.png'
plot sin(x)/x
replot sin(x)
Am I missing something at the end to get the second plot in my png file?
This is actually a very good question, and the behavior here is terminal dependent. Some terminals (e.g. postscript) will give you a new page for each replot. You have a couple of solutions...
First Option: You can make your plot prior to setting the terminal/output and then replot again after you set the terminal/output:
plot sin(x)/x
replot sin(x)
set terminal png
set output 'file.png
replot
This option is sometimes convenient if you want to plot the same thing in multiple terminals, but I rarely use it for anything else.
Second (better) Option: You can pack multiple plots into one command separating each with a comma.
set terminal png
set output 'file.png'
plot sin(x)/x, sin(x)
I very much prefer the second way -- when in a multiplot environment, this is the only way to put multiple graphs on the same plot. If you have very long functions to plot, you can break the line with gnuplot's line continuation (\ at the end of the line -- Nothing is allowed after the \, not even whitespace)
plot sin(x)/x with lines linecolor rgb "blue" linetype 7 lineweight 4, \
sin(x), \
cos(x)

gnuplot v4.4: Problem plotting using timeseries x axis

could someone please help me. I'm trying to create a simple chart.
set datafile separator ","
set xdata time
set timefmt "%d/%m/%Y %H:%M:%S"
set format x "%H:%M"
set autoscale y
plot ["13:00":"14:00"] './data.csv' using 1:2 with lines
data.csv:
26/10/2010 13:30:01,1
26/10/2010 13:30:12,2
26/10/2010 13:30:23,3
26/10/2010 13:30:34,4
gives me the error:
line 6: all points y value undefined!
I've tried all manners of timefmt settings!
Many thanks
The problem is defining the xrange, it needs to be the same format timefmt (see ?time_axis)
The following works for me
set datafile separator ","
set xdata time
set timefmt "%d/%m/%Y %H:%M:%S"
set format x "%H:%M"
set autoscale y
set xrange["26/10/2010 13:00:00":"26/10/2010 14:00:00"]
plot './data.csv' using 1:2 with lines
Oh, and I got rid of the blank lines in between each line of data.
If you don't want to edit the file to get rid of the blank data, then you can use awk in gnuplot like so,
plot "<awk '$0!~/^$/ {print $0}' ./data.csv" using 1:2 with lines
for the final command.
EDIT: further info in case there is still a problem (see the comments)
I needed to use the awk command to get the data to plot. This was to remove the blank lines in the data. Combining awk and gnuplot in this fashion works on linux systems, I'm not certain about gnuplot on windows. It could be that certain piping isn't happening, in which case you would have to remove the blank lines before using gnuplot. (you could still use awk for this, but perhaps not in the plot command?)
If you are using linux and the above is not working, then something else is the problem. Perhaps there are old commands stored in the gnuplot session? To make sure we are doing exactly the same thing, I give the shell script that I used (I changed the xrange to fit the data better and make a nicer plot, also notive the \$ instead of $, otherwise the shell misinterprets the $ sign).
Okay: I made the file data.csv.sh in a new folder (in case you have data.csv or data.csv.png files already),:
#!/bin/bash
echo "26/10/2010 13:30:01,1
26/10/2010 13:30:12,2
26/10/2010 13:30:23,3
26/10/2010 13:30:34,4" > "data.csv"
gnuplot<<EOF
set term png small
set output "data.csv.png"
set datafile separator ","
set xdata time
set timefmt "%d/%m/%Y %H:%M:%S"
set format x "%H:%M"
set autoscale y
set xrange["26/10/2010 13:30:00":"26/10/2010 13:31:00"]
plot "<awk '\$0!~/^\$/ {print \$0}' ./data.csv" using 1:2 with lines
set output
set term pop
EOF
Then on the terminal I typed:
chmod +wrx data.csv.sh && ./data.csv.sh
to make the shell script executable and then to run it.
The file data.csv.png looks like the following
All the best
Tom
I just wanted to add to the above discussion in case someone was having the same issues. I was trying to plot a time series, such as:
07/22/13 01:00 120
When I tried to plot using the above procedure I got the bad time format error. I changed my input data to:
07/22/2013 01:00 120
After the change, it ran perfectly. This would make sense because to gnuplot the 07/22/13 is vague. That is, is it 1913, 1813, or 2013 (or any other yy13).

Resources