Tempo Change JND

Craig Stuart Sapp <craig@ccrma.stanford.edu>
21-27 Nov 2005

Raw evaluation data

Data for 20 clicks starting at tempo 60 MM (1000 milliseconds between first two clicks):

data2060  =  {
{0.006,  
            3/3}, 
{0.005,  
            10/10}, 
{0.004,  
            35/40}, 
{0.003,  
            31/40}, 
{0.0024,  
            34/40}, 
{0.002,  
            25/40}, 
{0.0015,  
            28/40}, 
{0.001,  
            22/40}, 
{0.0005,  
            23/40}, 
{0.00025,  
            21/40}, 
{0.000125,  
            19/40}, 
{0.0000625,  
            4/8}
};

Data for 10 clicks starting at tempo 60 MM (1000 milliseconds between first two clicks):

data1060  =  {
{0.010,  
            38/40}, 
{0.008,  
            20/20}, 
{0.006,  
            37/40}, 
{0.005,  
            32/40}, 
{0.004,  
            28/40}, 
{0.003,  
            29/40}, 
{0.002,  
            27/40}, 
{0.001,  
            25/40}, 
{0.0005,  
            21/40}
};

Data for  5 clicks starting at tempo 60 MM (1000 milliseconds between first two clicks):

data0560  =  {
{0.016,  
            39/40}, 
{0.014,  
            38/40}, 
{0.010,  
            32/40}, 
{0.008,  
            30/40}, 
{0.006,  
            25/40}, 
{0.004,  
            23/40}
};

Data for  4 clicks starting at tempo 60 MM (1000 milliseconds between first two clicks):

data0460  =  {
{0.025,  
            19/20}, 
{0.020,  
            36/40}, 
{0.015, 
            48/61}, 
{0.012,  
            32/40}, 
{0.010,  
            29/40}, 
{0.008,  
            35/40}, 
{0.006,  
            27/40}
};

Data for  5 clicks starting at tempo 60 MM (1000 milliseconds between first two clicks):

data0360  =  {
{0.002,  
            23/40}, 
{0.004,  
            24/40}, 
{0.006,  
            24/40}, 
{0.008,  
            28/40}, 
{0.010,  
            24/40}, 
{0.015,  
            26/40}, 
{0.020, 
            25/40}, 
{0.025,  
            31/40}, 
{0.030,  
            30/40}, 
{0.035,  
            32/40}, 
{0.040,  
            31/40}, 
{0.045,  
            38/40}, 
{0.050,  
            39/40}
};

Plot of accuracy v acceleration rate

plot2060  =  
      ListPlot[data2060,  Frame -> True,  
        PlotStyle -> PointSize[0.02],  
        DisplayFunction -> Identity]; 
    plot1060  =  
      ListPlot[data1060,  Frame -> True,  
        PlotStyle -> {RGBColor[0, 0, 1], PointSize[0.02]}, 
        Axes -> False,  DisplayFunction -> Identity]; 
    plot0560  =  
      ListPlot[data0560,  Frame -> True,  
        PlotStyle -> {RGBColor[0, 1, 0], PointSize[0.02]}, 
        Axes -> False,  DisplayFunction -> Identity]; 
    plot0460  =  
      ListPlot[data0460,  Frame -> True,  
        PlotStyle -> {RGBColor[1, 0.6, 0], PointSize[0.02]}, 
        Axes -> False,  
        DisplayFunction -> Identity];
plot0360  =  
        ListPlot[data0360,  Frame -> True,  
          PlotStyle -> {RGBColor[0.75, 0, 0], PointSize[0.02]}, 
          Axes -> False,  
          DisplayFunction -> Identity];

Show[plot2060,  plot1060,  plot0560,  plot0360,  plot0460,  
        Graphics[{Dashing[{0.02,  0.02}],  
            Line[{{0, 0.75},  {0.51,  0.75}}]}],  
        PlotRange -> {{0,  0.055}, {0.45,  1. }},  
        DisplayFunction -> $DisplayFunction];

Examining the function of JND rate versus click count

Direct plotting of the data yeilds the following curve:

jnds  =  {{3, 0.0265},  {4,  0.0115},  {5,  0.0082},  {10,  
            0.0040},  {20,  0.0024}};

ListPlot[jnds,  Frame -> True,  Axes -> False,  
        PlotJoined -> True];

Taking the log of the JND fractions (on the vertical axis):

logjnds  =  
        Transpose[{Transpose[jnds][[1]],  
            Log[2, Transpose[
                  jnds][[2]]]} ];
ListPlot[logjnds,  Frame -> True,  Axes -> False, 
        PlotJoined -> True];

Taking the logarithm of both axes:

loglogjnds  =  
        Log[10,  Map[ {#[[1]],  #[[2]]} &,  jnds] ]  // 
          N;
ListPlot[loglogjnds,  Frame -> True,  Axes -> False, 
        PlotJoined -> True];

Since the number of clicks is greater than the number of tempo regions, subtract an offset before taking the log of the click counts.  An offset 
of one converts the clicks into tempo regions (3 clicks = 2 tempo regions):

loglogjnds  =  
        Log[10,  Map[ {#[[1]]  -  1,  #[[2]]} &,  jnds] ]  // 
          N;
ListPlot[loglogjnds,  Frame -> True,  Axes -> False, 
        PlotJoined -> True];

An offset of two removes the first click, and also removes the second click which describes the starting tempo, so an -2 removes the initial tempo from the display, and only counts the changing tempo regions.

loglogjnds  =  
        Log[10,  Map[ {#[[1]]  -  2,  #[[2]]} &,  jnds] ]  // 
          N;
ListPlot[loglogjnds,  Frame -> True,  Axes -> False, 
        PlotJoined -> True];

Now examine the offset of -2 and compare to a straight line, since it is fairly close to a line:

p0  =  
        ListPlot[loglogjnds,  Frame -> True,  Axes -> False,  
          PlotJoined -> True,  PlotRange -> All,  
          PlotStyle -> {Hue[0],  Thickness[0.02]}];

p1  =  
        ListPlot[loglogjnds,  Frame -> True,  Axes -> False,  
          PlotJoined -> False,  PlotRange -> All,  
          PlotStyle -> {PointSize[0.02],  
              Thickness[0.02]}];

fit  =  Fit[Drop[loglogjnds,  1],  {1,  x},  x]

fitp  =  
        Plot[fit,  {x,  0,  1.25},  Frame -> True,  
          Axes -> False,  
          PlotStyle -> Dashing[{0.02,  0.02}] ];

Show[p0,  p1,  fitp];

All the points except for the 3-click point fall fairly well onto a line. Now find the click count offset value which gives the closest shape to a straight line:

loglogjnds  =  
      Log[10,  Map[ {#[[1]]  -  2.66,  #[[2]]} &,  jnds] ]  // 
        N

p0  =  
        ListPlot[loglogjnds,  Frame -> True,  Axes -> False,  
          PlotJoined -> True,  PlotRange -> All,  
          PlotStyle -> {Hue[0],  Thickness[0.02]}];

p1  =  
        ListPlot[loglogjnds,  Frame -> True,  Axes -> False,  
          PlotJoined -> False,  PlotRange -> All,  
          PlotStyle -> {PointSize[0.02],  
              Thickness[0.02]}];

fit  =  Fit[loglogjnds,  {1,  x},  x]

fitp  =  
        Plot[fit,  {x,  -0.42,  1.25},  Frame -> True,  
          Axes -> False,  
          PlotStyle -> Dashing[{0.02,  0.02}] ];

Show[p0,  p1,  fitp];

Calculating the absolute time difference at final click between constant tempo and changed tempo at the JND points.

clicks  =  {3 , 4 , 5 , 10,  20};
jnd  =  {0.0265,  0.0115,  0.0082 , 0.0040,  
          0.0024};
both  =  Transpose[{clicks,  jnd}]

constantends  =  
      Map[(1000 * (#[[1]]  -  1)) &,  both]

slowertempos  =  
      Map[60000.0/(1000 * (#[[2]]  +  1)^(#[[1]] - 
                      1)) &,  both]

fastertempos  =  
      Map[60000.0/(1000 * (1 - #[[2]])^(#[[1]] - 
                      1)) &,  both]

slowerends  =  
      Map[Apply[Plus,  #] &,  
        Map[Table[
              1000.0 * (#[[2]]  +  1)^n,  {n,  
                0,  #[[1]] - 2}] &,  both]]

fasterends  =  
      Map[Apply[Plus,  #] &,  
        Map[Table[
              1000.0 * (1  -  #[[2]])^n,  {n,  
                0,  #[[1]] - 2}] &,  both]]

Now calculate the difference between a steady tempo and a tempo increasing or decreasing at the measured JND rate:

fasterends  - constantends

slowerends - constantends

Plot the slower tempo offset data versus the click count:

dataslow  =  
      Transpose[{clicks,  slowerends   -  constantends}]

pointplot  =  
        ListPlot[dataslow,  PlotStyle -> PointSize[0.02],  
          Axes -> False,  Frame -> True];

fit  =  Fit[dataslow,  {1,  x,  x^2},  x]

fitplot  =  
        Plot[fit,  {x,  0,  20},  Frame -> True];

A parabolic cuve fits the data well with slight flattening at the bottom (cannot have a negative data point as the curve predicts on the far right). Anyway, it is not possible to go below 3 clicks for this type of test.

Show[pointplot,  fitplot];

Plotting final tempo for JND rate versus click count

fastpoints  =  Transpose[{clicks, fastertempos}]

fpoints  =  
        ListPlot[fastpoints,  Frame -> True,  Axes -> False,  
          PlotStyle -> PointSize[0.02]];

flines  =  
        ListPlot[fastpoints,  Frame -> True,  Axes -> False,  
          PlotJoined -> True, 
          PlotStyle -> {Hue[0], Thickness[0.02]}];

ffit  =  
      Fit[{fastpoints[[4]],  fastpoints[[5]]},  {1,  x},  
        x]

If the number of clicks in the test data is 1000, then the predicted final tempo at the JND would be about 120 MM.  Since the starting tempo was 60 MM, it is probable that this estimate is too large.

ffit  /.  x ->  1000

Here is an extra line through the 10 and 20 click points just for reference:

ffitplot  =  
        Plot[ffit,  {x, 5,  20},  
          PlotStyle -> Dashing[{0.02,  0.02}],  Frame -> True,  
          Axes -> False];

Show[flines,  fpoints,  ffitplot];

Calculating the ending tempos for various gains

Calculate the ending tempo at various gains for the 20 click sound example:

nn  =  20;
string  =  "data"  <>  
          ToString[nn]  <>  "60";
tdata  =  
        ToExpression[string]  //  N;
min  =  
        Map[(60000/ (1000.0  (1 + #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
max  =  
        Map[(60000/ (1000.0  (1 - #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
table  =  
        Transpose[{Transpose[tdata][[1]],  Transpose[
                  tdata][[2]],  min,  max}] // 
          N;
table  //  TableForm

Calculate the ending tempo at various gains for the 10 click sound example:

nn  =  10;
string  =  "data"  <>  
          ToString[nn]  <>  "60";
tdata  =  
        ToExpression[string]  //  N;
min  =  
        Map[(60000/ (1000.0  (1 + #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
max  =  
        Map[(60000/ (1000.0  (1 - #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
table  =  
        Transpose[{Transpose[tdata][[1]],  Transpose[
                  tdata][[2]],  min,  max}] // 
          N;
table  //  TableForm

Calculate the ending tempo at various gains for the 5 click sound example:

nn  =  5;
string  =  "data0"  <>   ToString[
            nn]  <>  "60";
tdata  =  
        ToExpression[string]  //  N;
min  =  
        Map[(60000/ (1000.0  (1 + #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
max  =  
        Map[(60000/ (1000.0  (1 - #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
table  =  
        Transpose[{Transpose[tdata][[1]],  Transpose[
                  tdata][[2]],  min,  max}] // 
          N;
table  //  TableForm

Calculate the ending tempo at various gains for the 4 click sound example:

nn  =  4;
string  =  "data0"  <>   ToString[
            nn]  <>  "60";
tdata  =  
        ToExpression[string]  //  N;
min  =  
        Map[(60000/ (1000.0  (1 + #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
max  =  
        Map[(60000/ (1000.0  (1 - #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
table  =  
        Transpose[{Transpose[tdata][[1]],  Transpose[
                  tdata][[2]],  min,  max}] // 
          N;
table  //  TableForm

Calculate the ending tempo at various gains for the 3 click sound example:

nn  =  3;
string  =  "data0"  <>   ToString[
            nn]  <>  "60";
tdata  =  
        ToExpression[string]  //  N;
min  =  
        Map[(60000/ (1000.0  (1 + #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
max  =  
        Map[(60000/ (1000.0  (1 - #)^(nn - 
                          1))) &,  Transpose[
              tdata][[1]]];
table  =  
        Transpose[{Transpose[tdata][[1]],  Transpose[
                  tdata][[2]],  min,  max}] // 
          N;
table  //  TableForm