Search code examples
plotgnuplot

Set custom xtic labels as a function of the xtic value


Question

How can I apply a conversion function to the indices of a datafile to generate the xticlabels at each interval?

Here I'd like to use my user-defined secs_to_beats_bars function (which returns a string formatted with sprintf), see below.

Note, I am seeking to avoid the following:

  • manually creating a list of arbitrary tick labels and apply them at specific locations
  • editing or recreating the original datafile

For example

Lets say I have a datafile, indexed in seconds (decimal):

# Seconds, Pitch, Duration, Velocity
0.16129032258064513, 31, 0.7157258064516129, 71 
0.8870967741935483, 31, 0.3578629032258066, 75 
1.2499999999999998, 31, 0.3427419354838712, 78 
1.6129032258064513, 42, 0.4637096774193551, 75 
2.0967741935483866, 37, 0.7056451612903228, 76 
2.82258064516129, 33, 0.3427419354838712, 77 
3.1854838709677415, 31, 0.3427419354838712, 78 
3.548387096774193, 31, 0.46370967741935554, 74 
4.032258064516128, 31, 1.0735887096774201, 71 
5.120967741935483, 33, 0.34778225806451696, 62 
5.483870967741934, 31, 0.32258064516129087, 50 
5.806451612903225, 35, 0.48387096774193544, 87 
5.96774193548387, 43, 0.3528225806451618, 63 
6.330645161290321, 31, 0.7106854838709676, 58 

I have a custom function to convert the seconds to a musically relevant time system (like beats and bars):

# tempo is in beats per minute, beats and bars are indexed from one.
# eg: bar 1 beat 1 = "1:1"

secs_to_beats(secs, tempo) = int(secs * tempo / 60);
secs_to_beats_bars(secs, tempo, beatsperbar) = sprintf('%.0f:%.0f', int(secs_to_beats(secs, tempo) / beatsperbar) + 1, secs_to_beats(secs, tempo) % beatsperbar + 1);

Which I use to set the xtics at a regular intervals at each beat, and mxtics to further subdivide the grid to semiquavers:

beats_to_secs(beat, tempo) = 1.0/(tempo * 1.0/60.0) * beat;
bpm = 80;
beatinterval = beats_to_secs(1, bpm);
set xtics beatinterval; set mxtics 4; set grid x mx ls 100, ls 101;

and finally plot this using boxxyerror to create rectangles for each row using the seconds, pitch and duration columns:

set yrange[0:127]; 
plot 'midi.dat' u (($1+$3)/2):(($2+0.95)/2):1:($3+$1):($2):($2+0.95) w boxxy fs solid 0.50 border lc 'black';

Generated plot

As you can see, the xticlabels are in seconds, I am seeking to convert these to the beats/bars using the secs_to_beats_bars function above.

generated plot


Solution

  • I guess the following example does what you are asking for:

    • no manual tic labels
    • no change of original data
    • x-axis in bars instead of seconds

    Furthermore:

    • you can set an offset if the first note is not exactly on the beat.
    • I used the 4-column syntax of the plotting style boxxyerror (x):(y):(+/-dx):(+/-dy), check help boxxyerror.

    With your added factors 1.0 in your script, I assume that you are aware about gnuplot's integer division (e.g. 1/2 = 0).

    Script:

    ### change x-axis scale
    reset session
    
    $Data <<EOD
    # Seconds, Pitch, Duration, Velocity
    0.16129032258064513, 31, 0.7157258064516129, 71 
    0.8870967741935483, 31, 0.3578629032258066, 75 
    1.2499999999999998, 31, 0.3427419354838712, 78 
    1.6129032258064513, 42, 0.4637096774193551, 75 
    2.0967741935483866, 37, 0.7056451612903228, 76 
    2.82258064516129, 33, 0.3427419354838712, 77 
    3.1854838709677415, 31, 0.3427419354838712, 78 
    3.548387096774193, 31, 0.46370967741935554, 74 
    4.032258064516128, 31, 1.0735887096774201, 71 
    5.120967741935483, 33, 0.34778225806451696, 62 
    5.483870967741934, 31, 0.32258064516129087, 50 
    5.806451612903225, 35, 0.48387096774193544, 87 
    5.96774193548387, 43, 0.3528225806451618, 63 
    6.330645161290321, 31, 0.7106854838709676, 58 
    EOD
    
    BPM = 80     # beats per minute
    BPB = 4      # beats per bar
    OFF = 0.000  # offset in seconds
    
    SecToBar(s)          = s*BPM/60./BPB
    BoxCenter(colS,colD) = SecToBar(column(colS) + column(colD)*0.5 + OFF) + 1
    BoxWidth(colD)       = SecToBar(column(colD))*0.5
    
    set datafile separator comma
    set style line 1 lw 1.5 lc "red"
    set style line 2 lw 1.0 lc "grey"
    set style fill solid 0.50 transparent border lc 'black'
    set key noautotitle
    
    set xlabel "bars"
    set xtics 1
    set mxtics BPB
    set grid x,mx ls 1, ls 2
    set ylabel "pitch"
    set yrange[25:50]
    
    plot $Data u (BoxCenter(1,3)):2:(BoxWidth(3)):(0.45) w boxxy lc 'blue'
    ### end of script
    

    Result:

    enter image description here