how to draw lines in Pine script (Tradingview)? - line

Pine editor still does not have built-in functions to plot lines (such as support lines, trend lines).
I could not find any direct or indirect method to draw lines.
I want to build function that look like below (for example only)
draw_line(price1, time1,price2, time2)
any Ideas or suggestions ?

Unfortunately I don't think this is something they want to provide. Noticing several promising posts from 4 years ago that never came through. The only other way, seem to involve some calculations, by approximating your line with some line plots, where you hide the non-relevant parts.
For example:
...
c = close >= open ? lime : red
plot(close, color = c)
would produce something like this:
Then, you could try to replace red with na to get only the green parts.
Example 2
I've done some more experiments. Apparently Pine is so crippled you can't even put a plot in function, so the only way seem to be to use the point slope formula for a line, like this:
//#version=3
study(title="Simple Line", shorttitle='AB', overlay=true)
P1x = input(5744)
P1y = input(1.2727)
P2x = input(5774)
P2y = input(1.2628)
plot(n, color=na, style=line) // hidden plot to show the bar number in indicator
// point slope
m = - (P2y - P1y) / (P2x - P1x)
// plot range
AB = n < P1x or n > P2x ? na : P1y - m*(n - P1x)
LA = (n == P1x) ? P1y : na
LB = (n == P2x) ? P2y : na
plot(AB, title="AB", color=#ff00ff, linewidth=1, style=line, transp=0)
plotshape(LA, title='A', location=location.absolute, color=silver, transp=0, text='A', textcolor=black, style=shape.labeldown)
plotshape(LB, title='B', location=location.absolute, color=silver, transp=0, text='B', textcolor=black, style=shape.labelup )
The result is quite nice, but too inconvenient to use.
UPDATE: 2019-10-01
Apparently they have added some new line functionality to Pinescript 4.0+.
Here is an example of using the new vline() function:
//#version=4
study("vline() Function for Pine Script v4.0+", overlay=true)
vline(BarIndex, Color, LineStyle, LineWidth) => // Verticle Line, 54 lines maximum allowable per indicator
return = line.new(BarIndex, -1000, BarIndex, 1000, xloc.bar_index, extend.both, Color, LineStyle, LineWidth)
if(bar_index%10==0.0)
vline(bar_index, #FF8000ff, line.style_solid, 1) // Variable assignment not required
As for the other "new" line function, I have not tested it yet.

This is now possible in Pine Script v4:
//#version=4
study("Line", overlay=true)
l = line.new(bar_index, high, bar_index[10], low[10], width = 4)
line.delete(l[1])
Here is a vertical line function by midtownsk8rguy on TradingView:
vline(BarIndex, Color, LineStyle, LineWidth) => // Verticle Line Function, ≈50-54 lines maximum allowable per indicator
// return = line.new(BarIndex, 0.0, BarIndex, 100.0, xloc.bar_index, extend.both, Color, LineStyle, LineWidth) // Suitable for study(overlay=false) and RSI, Stochastic, etc...
// return = line.new(BarIndex, -1.0, BarIndex, 1.0, xloc.bar_index, extend.both, Color, LineStyle, LineWidth) // Suitable for study(overlay=false) and +/-1.0 oscillators
return = line.new(BarIndex, low - tr, BarIndex, high + tr, xloc.bar_index, extend.both, Color, LineStyle, LineWidth) // Suitable for study(overlay=true)
if(bar_index%10==0.0) // Generically plots a line every 10 bars
vline(bar_index, #FF8000ff, line.style_solid, 1) // Variable assignment not required
You can also use if barstate.islast if you only draw your lines once instead of on each candle, this way you don't need to delete the previous lines.

More compact code for draw lines:
//#version=3
study("Draw line", overlay=true)
plot(n, color=na, style=line)
AB(x1,x2,y1,y2) => n < x1 or n > x2 ? na : y1 + (y2 - y1) / (x2 - x1) * (n - x1)
plot(AB(10065,10136,3819,3893), color=#ff00ff, linewidth=1, style=line,
transp=0)
plot(AB(10091,10136,3966.5,3931), color=#ff00ff, linewidth=1, style=line,
transp=0)

Here is an example that might answer the original question:
//#version=4
study(title="trendline example aapl", overlay=true)
//#AAPL
line12= line.new(x1=int(1656322200000),
y1=float(143.49),
x2=int(1659519000000),
y2=float(166.59),
extend=extend.right,
xloc=xloc.bar_time)
(to calculate the time it needs to be calculated as the *bar open time in unix milliseconds see: https://currentmillis.com/ ; can be calculated in excel with this formula =
= (([date eg mm/dd/yyyy]+[bar open time eg 9.30am])- 0/24 - DATE(1970,1,1)) * 86400000
= ((6/27/2022+9:30:00 AM)- 0/24 - DATE(1970,1,1)) * 86400000
= ((44739+0.395833333333333)- 0/24 - DATE(1970,1,1)) * 86400000
= 1656322200000
)
adjust the zero/24 to offset the time zone if needed eg 1/24

Related

How to outline a histogram with a color and add a bell curve on ggplot2

I have been trying to add a bell curve to my histogram an outline it with a color so that it is more pleasing. enter image description here
I have added what my histogram looks like to give someone an idea on what I am working with, also here is my code thus far, thank you in advance.
ggplot(data = mammal.data.22.select2)+
geom_histogram(aes(x=Time, fill=Species))+
scale_fill_manual(values=c("paleturquoise4", "turquoise2"))+
facet_wrap(~Species, nrow=1)+
ylab("Observations")+
xlab("Time of Day")+
theme(strip.text.x = element_blank())
Let's build a histogram with a build-in dataset that seems similar-ish to your data structure.
library(ggplot2)
binwidth <- 0.25
p <- ggplot(iris, aes(Petal.Length)) +
geom_histogram(
aes(fill = Species),
binwidth = binwidth,
alpha = 0.5
) +
facet_wrap(~ Species)
You can use stat_bin() + geom_step() to give an outline to the histogram, without colouring the edge of every rectangle in the histogram. The only downside is that the first and last bins don't touch the x-axis.
p + stat_bin(
geom = "step", direction = "mid",
aes(colour = Species), binwidth = binwidth
)
To overlay a density function with a histogram, you could calculate the relevant parameters yourself and use stat_function() with fun = dnorm repeatedly. Alternatively, you can use ggh4x::stat_theodensity() to achieve a similar thing. Note that whether you use stat_function() or stat_theodensity(), you should scale the density back to the counts that your histogram uses (or scale histogram to density). In the example below, we do that by using after_stat(count * binwidth).
p + ggh4x::stat_theodensity(
aes(colour = Species,
y = after_stat(count * binwidth))
)
Created on 2022-04-15 by the reprex package (v2.0.1)
(disclaimer: I'm the author of ggh4x)

Stimuli changes with every frame being displayed.

I have a bit of code (displayed below) that is supposed to display the stimulus for 10 frames. We need pretty exact display times, so using number of frames is a must instead of core.wait(xx) as the display time won't be as precise.
Instead of drawing the stimuli, and leaving it for another 9 frames - the stimuli is re-drawn for every frame.
# Import what is needed
import numpy as np
from psychopy import visual, event, core, logging
from math import sin, cos
import random, math
win = visual.Window(size=(1366, 768), fullscr=True, screen=0, allowGUI=False, allowStencil=False,
monitor='testMonitor', color=[0,0,0], colorSpace='rgb',
blendMode='avg', useFBO=True,
units='deg')
### Definitions of libraries
'''Parameters :
numpy - python package of numerical computations
visual - where all visual stimulus live
event - code to deal with mouse + keyboard input
core - general function for timing & closing the program
logging - provides function for logging error and other messages to one file
random - options for creating arrays of random numbers
sin & cos - for geometry and trigonometry
math - mathematical operations '''
# this is supposed to record all frames
win.setRecordFrameIntervals(True)
win._refreshThreshold=1/65.0+0.004 #i've got 65Hz monitor and want to allow 4ms tolerance
#set the log module to report warnings to the std output window (default is errors only)
logging.console.setLevel(logging.WARNING)
nIntervals=5
# Create space variables and a window
lineSpaceX = 0.55
lineSpaceY = 0.55
patch_orientation = 45 # zero is vertical, going anti-clockwise
surround_orientation = 90
#Jitter values
g_posJitter = 0.05 #gaussian positional jitter
r_posJitter = 0.05 #random positional jitter
g_oriJitter = 5 #gaussian orientation jitter
r_oriJitter = 5 #random orientation jitter
#create a 1-Dimentional array
line = np.array(range(38)) #with values from (0-37) #possibly not needed 01/04/16 DK
#Region where the rectangular patch would appear
#x_rand=random.randint(1,22) #random.randint(Return random integers from low (inclusive) to high (exclusive).
#y_rand=random.randint(1,25)
x_rand=random.randint(6,13) #random.randint(Return random integers from low (inclusive) to high (inclusive).
y_rand=random.randint(6,16)
#rectangular patch dimensions
width=15
height=12
message = visual.TextStim(win,pos=(0.0,-12.0),text='...Press SPACE to continue...')
fixation = visual.TextStim(win, pos=(0.0,0.0), text='X')
# Initialize clock to record response time
rt_clock = core.Clock()
#Nested loop to draw anti-aliased lines on grid
#create a function for this
def myStim():
for x in xrange(1,33): #32x32 grid. When x is 33 will not execute loop - will stop
for y in xrange(1,33): #When y is 33 will not execute loop - will stop
##Define x & y value (Gaussian distribution-positional jitter)
x_pos = (x-32/2-1/2 )*lineSpaceX + random.gauss(0,g_posJitter) #random.gauss(mean,s.d); -1/2 is to center even-numbered stimuli; 32x32 grid
y_pos = (y-32/2-1/2 )*lineSpaceY + random.gauss(0,g_posJitter)
if (x >= x_rand and x < x_rand+width) and (y >= y_rand and y < y_rand+height): # note only "=" on one side
Line_Orientation = random.gauss(patch_orientation,g_oriJitter) #random.gauss(mean,s.d) - Gaussian func.
else:
Line_Orientation = random.gauss(surround_orientation,g_oriJitter) #random.gauss(mean,s.d) - Gaussian func.
#Line_Orientation = random.gauss(Line_Orientation,g_oriJitter) #random.gauss(mean,s.d) - Gaussian func.
#stimOri = random.uniform(xOri - r_oriJitter, xOri + r_oriJitter) #random.uniform(A,B) - Uniform func.
visual.Line(win, units = "deg", start=(0,0), end=(0.0,0.35), pos=(x_pos,y_pos), ori=Line_Orientation, autoLog=False).draw() #Gaussian func.
for frameN in range (10):
myStim()
win.flip()
print x_rand, y_rand
print keys, rt #display response and reaction time on screen output window
I have tried to use the following code to keep it displayed (by not clearing the buffer). But it just draws over it several times.
for frameN in range(10):
myStim()
win.flip(clearBuffer=False)
I realize that the problem could be because I have .draw() in the function that I have defined def myStim():. However, if I don't include the .draw() within the function - I won't be able to display the stimuli.
Thanks in advance for any help.
If I understand correctly, the problem you are facing is that you have to re-draw the stimulus on every flip, but your current drawing function also recreates the entire (random) stimulus, so:
the stimulus changes on each draw between flips, although you need it to stay constant, and
you get a (on some systems quite massive) performance penalty by re-creating the entire stimulus over and over again.
What you want instead is: create the stimulus once, in its entirety, before presentation; and then have this pre-generated stimulus drawn on every flip.
Since your stimulus consists of a fairly large number of visual elements, I would suggest using a class to store the stimulus in one place.
Essentially, you would replace your myStim() function with this class (note that I stripped out most comments, re-aligned the code a bit, and simplified the if statement):
class MyStim(object):
def __init__(self):
self.lines = []
for x in xrange(1, 33):
for y in xrange(1, 33):
x_pos = ((x - 32 / 2 - 1 / 2) * lineSpaceX +
random.gauss(0, g_posJitter))
y_pos = ((y - 32 / 2 - 1 / 2) * lineSpaceY +
random.gauss(0, g_posJitter))
if ((x_rand <= x < x_rand + width) and
(y_rand <= y < y_rand + height)):
Line_Orientation = random.gauss(patch_orientation,
g_oriJitter)
else:
Line_Orientation = random.gauss(surround_orientation,
g_oriJitter)
current_line = visual.Line(
win, units="deg", start=(0, 0), end=(0.0, 0.35),
pos=(x_pos, y_pos), ori=Line_Orientation,
autoLog=False
)
self.lines.append(current_line)
def draw(self):
[line.draw() for line in self.lines]
What this code does on instantiation is in principle identical to your myStim() function: it creates a set of (random) lines. But instead of drawing them onto the screen right away, they are all collected in the list self.lines, and will remain there until we actually need them.
The draw() method traverses through this list, element by element (that is, line by line), and calls every line's draw() method. Note that the stimuli do not have to be re-created every time we want to draw the whole set, but instead we just draw the already pre-created lines!
To get this working in practice, you first need to instantiate the MyStim class:
myStim = MyStim()
Then, whenever you want to present the stimulus, all you have to do is
myStim.draw()
win.flip()
Here is the entire, modified code that should get you started:
import numpy as np
from psychopy import visual, event, core, logging
from math import sin, cos
import random, math
win = visual.Window(size=(1366, 768), fullscr=True, screen=0, allowGUI=False, allowStencil=False,
monitor='testMonitor', color=[0,0,0], colorSpace='rgb',
blendMode='avg', useFBO=True,
units='deg')
# this is supposed to record all frames
win.setRecordFrameIntervals(True)
win._refreshThreshold=1/65.0+0.004 #i've got 65Hz monitor and want to allow 4ms tolerance
#set the log module to report warnings to the std output window (default is errors only)
logging.console.setLevel(logging.WARNING)
nIntervals=5
# Create space variables and a window
lineSpaceX = 0.55
lineSpaceY = 0.55
patch_orientation = 45 # zero is vertical, going anti-clockwise
surround_orientation = 90
#Jitter values
g_posJitter = 0.05 #gaussian positional jitter
r_posJitter = 0.05 #random positional jitter
g_oriJitter = 5 #gaussian orientation jitter
r_oriJitter = 5 #random orientation jitter
x_rand=random.randint(6,13) #random.randint(Return random integers from low (inclusive) to high (inclusive).
y_rand=random.randint(6,16)
#rectangular patch dimensions
width=15
height=12
message = visual.TextStim(win,pos=(0.0,-12.0),text='...Press SPACE to continue...')
fixation = visual.TextStim(win, pos=(0.0,0.0), text='X')
# Initialize clock to record response time
rt_clock = core.Clock()
class MyStim(object):
def __init__(self):
self.lines = []
for x in xrange(1, 33):
for y in xrange(1, 33):
x_pos = ((x - 32 / 2 - 1 / 2) * lineSpaceX +
random.gauss(0, g_posJitter))
y_pos = ((y - 32 / 2 - 1 / 2) * lineSpaceY +
random.gauss(0, g_posJitter))
if ((x_rand <= x < x_rand + width) and
(y_rand <= y < y_rand + height)):
Line_Orientation = random.gauss(patch_orientation,
g_oriJitter)
else:
Line_Orientation = random.gauss(surround_orientation,
g_oriJitter)
current_line = visual.Line(
win, units="deg", start=(0, 0), end=(0.0, 0.35),
pos=(x_pos, y_pos), ori=Line_Orientation,
autoLog=False
)
self.lines.append(current_line)
def draw(self):
[line.draw() for line in self.lines]
myStim = MyStim()
for frameN in range(10):
myStim.draw()
win.flip()
# Clear the screen
win.flip()
print x_rand, y_rand
core.quit()
Please do note that even with this approach, I am dropping frames on a 3-year-old laptop computer with relatively weak integrated graphics chip. But I suspect a modern, fast GPU would be able to handle this amount of visual objects just fine. In the worst case, you could pre-create a large set of stimuli, save them as a bitmap file via win.saveMovieFrames(), and present them as a pre-loaded SimpleImageStim during your actual study.

Description of parameters of GDAL SetGeoTransform

Can anyone help me with parameters for SetGeoTransform? I'm creating raster layers with GDAL, but I can't find description of 3rd and 5th parameter for SetGeoTransform. It should be definition of x and y axis for cells. I try to find something about it here and here, but nothing.
I need to find description of these two parameters... It's a value in degrees, radians, meters? Or something else?
The geotransform is used to convert from map to pixel coordinates and back using an affine transformation. The 3rd and 5th parameter are used (together with the 2nd and 4th) to define the rotation if your image doesn't have 'north up'.
But most images are north up, and then both the 3rd and 5th parameter are zero.
The affine transform consists of six coefficients returned by
GDALDataset::GetGeoTransform() which map pixel/line coordinates into
georeferenced space using the following relationship:
Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2)
Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5)
See the section on affine geotransform at:
https://gdal.org/tutorials/geotransforms_tut.html
I did do like below code.
As a result I was able to do same with SetGeoTransform.
# new file
dst = gdal.GetDriverByName('GTiff').Create(OUT_PATH, xsize, ysize, band_num, dtype)
# old file
ds = gdal.Open(fpath)
wkt = ds.GetProjection()
gcps = ds.GetGCPs()
dst.SetGCPs(gcps, wkt)
...
dst.FlushCache()
dst = Nonet
Given information from the aforementioned gdal datamodel docs, the 3rd & 5th parameters of SatGeoTransform (x_skew and y_skew respectively) can be calculated from two control points (p1, p2) with known x and y in both "geo" and "pixel" coordinate spaces. p1 should be above-left of p2 in pixelspace.
x_skew = sqrt((p1.geox-p2.geox)**2 + (p1.geoy-p2.geoy)**2) / (p1.pixely - p2.pixely)`
y_skew = sqrt((p1.geox-p2.geox)**2 + (p1.geoy-p2.geoy)**2) / (p1.pixelx - p2.pixelx)`
In short this is the ratio of Euclidean distance between the points in geospace to the height (or width) of the image in pixelspace.
The units of the parameters are "geo"length/"pixel"length.
Here is a demonstration using the corners of the image stored as control points (gcps):
import gdal
from math import sqrt
ds = gdal.Open(fpath)
gcps = ds.GetGCPs()
assert gcps[0].Id == 'UpperLeft'
p1 = gcps[0]
assert gcps[2].Id == 'LowerRight'
p2 = gcps[2]
y_skew = (
sqrt((p1.GCPX-p2.GCPX)**2 + (p1.GCPY-p2.GCPY)**2) /
(p1.GCPPixel - p2.GCPPixel)
)
x_skew = (
sqrt((p1.GCPX-p2.GCPX)**2 + (p1.GCPY-p2.GCPY)**2) /
(p1.GCPLine - p2.GCPLine)
)
x_res = (p2.GCPX - p1.GCPX) / ds.RasterXSize
y_res = (p2.GCPY - p1.GCPY) / ds.RasterYSize
ds.SetGeoTransform([
p1.GCPX,
x_res,
x_skew,
p1.GCPY,
y_skew,
y_res,
])

implementing ease in update loop

I want to animate a sprite from point y1 to point y2 with some sort of deceleration. when it reaches point y2, the speed of the object will be 0 so it will completely stop.
I Know the two points, and I know the object's starting speed.
The animation time is not so important to me. I can decide on it if needed.
for example: y1 = 0, y2 = 400, v0 = 250 pixels per second (= starting speed)
I read about easing functions but I didn't understand how do I actually implement it in the
update loop.
here's my update loop code with the place that should somehow implement an easing function.
-(void)onTimerTick{
double currentTime = CFAbsoluteTimeGetCurrent() ;
float timeDelta = self.lastUpdateTime - currentTime;
self.lastUpdateTime = currentTime;
float *pixelsToMove = ???? // here needs to be some formula using v0, timeDelta, y2, y1
sprite.y += pixelsToMove;
}
Timing functions as Bézier curves
An easing timing function is basically a Bézier curve from (0,0) to (1,1) where the horizontal axis is "time" and the vertical axis is "amount of change". Since a Bézier curve mathematically is as
start*(1-t)^3 + c1*t(1-t)^2 + c2*t^2(1-t) + end*t^3
you can insert any time value and get the amount of change that should be applied. Note that both time and change is normalized (in the range of 0 to 1).
Note that the variable t is not the time value, t is how far along the curve you have come. The time value is the x value of the point along the curve.
The curve below is a sample "ease" curve that starts off slow, goes faster and slows down in the end.
If for example a third of the time had passed you would calculate what amount of change that corresponds to be update the value of the animated property as
currentValue = beginValue + amountOfChange*(endValue-beginValue)
Example
Say you are animating the position from (50, 50) to (200, 150) using a curve with control points at (0.6, 0.0) and (0.5, 0.9) and a duration of 4 seconds (the control points are trying to be close to that of the image above).
When 1 second of the animation has passed (25% of total duration) the value along the curve is:
(0.25,y) = (0,0)*(1-t)^3 + (0.6,0)*t(1-t)^2 + (0.5,0.9)*t^2(1-t) + (1,1)*t^3
This means that we can calculate t as:
0.25 = 0.6*t(1-t)^2 + 0.5*t^2(1-t) + t^3
Wolfram Alpha tells me that t = 0.482359
If we the input that t in
y = 0.9*t^2*(1-t) + t^3
we will get the "amount of change" for when 1 second of the duration has passed.
Once again Wolfram Alpha tells me that y = 0.220626 which means that 22% of the value has changed after 25% of the time. This is because the curve starts out slow (you can see in the image that it is mostly flat in the beginning).
So finally: 1 second into the animation the position is
(x, y) = (50, 50) + 0.220626 * (200-50, 150-50)
(x, y) = (50, 50) + 0.220626 * (150, 100)
(x, y) = (50, 50) + (33.0939, 22.0626)
(x, y) = (50+33.0939, 50+22.0626)
(x, y) = (83.0939, 72.0626)
I hope this example helps you understanding how to use timing functions.

The math behind Apple's Speak here example

I have a question regarding the math that Apple is using in it's speak here example.
A little background: I know that average power and peak power returned by the AVAudioRecorder and AVAudioPlayer is in dB. I also understand why the RMS power is in dB and that it needs to be converted into amp using pow(10, (0.5 * avgPower)).
My question being:
Apple uses this formula to create it's "Meter Table"
MeterTable::MeterTable(float inMinDecibels, size_t inTableSize, float inRoot)
: mMinDecibels(inMinDecibels),
mDecibelResolution(mMinDecibels / (inTableSize - 1)),
mScaleFactor(1. / mDecibelResolution)
{
if (inMinDecibels >= 0.)
{
printf("MeterTable inMinDecibels must be negative");
return;
}
mTable = (float*)malloc(inTableSize*sizeof(float));
double minAmp = DbToAmp(inMinDecibels);
double ampRange = 1. - minAmp;
double invAmpRange = 1. / ampRange;
double rroot = 1. / inRoot;
for (size_t i = 0; i < inTableSize; ++i) {
double decibels = i * mDecibelResolution;
double amp = DbToAmp(decibels);
double adjAmp = (amp - minAmp) * invAmpRange;
mTable[i] = pow(adjAmp, rroot);
}
}
What are all the calculations - or rather, what do each of these steps do? I think that mDecibelResolution and mScaleFactor are used to plot 80dB range over 400 values (unless I'm mistaken). However, what's the significance of inRoot, ampRange, invAmpRange and adjAmp? Additionally, why is the i-th entry in the meter table "mTable[i] = pow(adjAmp, rroot);"?
Any help is much appreciated! :)
Thanks in advance and cheers!
It's been a month since I've asked this question, and thanks, Geebs, for your response! :)
So, this is related to a project that I've been working on, and the feature that is based on this was implemented about 2 days after asking that question. Clearly, I've slacked off on posting a closing response (sorry about that). I posted a comment on Jan 7, as well, but circling back, seems like I had a confusion with var names. >_<. Thought I'd give a full, line by line answer to this question (with pictures). :)
So, here goes:
//mDecibelResolution is the "weight" factor of each of the values in the meterTable.
//Here, the table is of size 400, and we're looking at values 0 to 399.
//Thus, the "weight" factor of each value is minValue / 399.
MeterTable::MeterTable(float inMinDecibels, size_t inTableSize, float inRoot)
: mMinDecibels(inMinDecibels),
mDecibelResolution(mMinDecibels / (inTableSize - 1)),
mScaleFactor(1. / mDecibelResolution)
{
if (inMinDecibels >= 0.)
{
printf("MeterTable inMinDecibels must be negative");
return;
}
//Allocate a table to store the 400 values
mTable = (float*)malloc(inTableSize*sizeof(float));
//Remember, "dB" is a logarithmic scale.
//If we have a range of -160dB to 0dB, -80dB is NOT 50% power!!!
//We need to convert it to a linear scale. Thus, we do pow(10, (0.05 * dbValue)), as stated in my question.
double minAmp = DbToAmp(inMinDecibels);
//For the next couple of steps, you need to know linear interpolation.
//Again, remember that all calculations are on a LINEAR scale.
//Attached is an image of the basic linear interpolation formula, and some simple equation solving.
//As per the image, and the following line, (y1 - y0) is the ampRange -
//where y1 = maxAmp and y0 = minAmp.
//In this case, maxAmp = 1amp, as our maxDB is 0dB - FYI: 0dB = 1amp.
//Thus, ampRange = (maxAmp - minAmp) = 1. - minAmp
double ampRange = 1. - minAmp;
//As you can see, invAmpRange is the extreme right hand side fraction on our image's "Step 3"
double invAmpRange = 1. / ampRange;
//Now, if we were looking for different values of x0, x1, y0 or y1, simply substitute it in that equation and you're good to go. :)
//The only reason we were able to get rid of x0 was because our minInterpolatedValue was 0.
//I'll come to this later.
double rroot = 1. / inRoot;
for (size_t i = 0; i < inTableSize; ++i) {
//Thus, for each entry in the table, multiply that entry with it's "weight" factor.
double decibels = i * mDecibelResolution;
//Convert the "weighted" value to amplitude using pow(10, (0.05 * decibelValue));
double amp = DbToAmp(decibels);
//This is linear interpolation - based on our image, this is the same as "Step 3" of the image.
double adjAmp = (amp - minAmp) * invAmpRange;
//This is where inRoot and rroot come into picture.
//Linear interpolation gives you a "straight line" between 2 end-points.
//rroot = 0.5
//If I raise a variable, say myValue by 0.5, it is essentially taking the square root of myValue.
//So, instead of getting a "straight line" response, by storing the square root of the value,
//we get a curved response that is similar to the one drawn in the image (note: not to scale).
mTable[i] = pow(adjAmp, rroot);
}
}
Response Curve image: As you can see, the "Linear curve" is not exactly a curve. >_<
Hope this helps the community in some way. :)
No expert, but based on physics and math:
Assume the max amplitude is 1 and minimum is 0.0001 [corresponding to -80db, which is what min db value is set to in the apple example : #define kMinDBvalue -80.0 in AQLevelMeter.h]
minAmp is the minimum amplitude = 0.0001 for this example
Now, all that is being done is the amplitudes in multiples of the decibel resolution are being adjusted against the minimum amplitude:
adjusted amplitude = (amp-minamp)/(1-minamp)
This makes the range of the adjusted amplitude = 0 to 1 instead of 0.0001 to 1 (if that was desired).
inRoot is set to 2 here. rroot=1/2 - raising to power 1/2 is square root. from apple's file:
// inRoot - this controls the curvature of the response. 2.0 is square root, 3.0 is cube root. But inRoot doesn't have to be integer valued, it could be 1.8 or 2.5, etc.
Essentially gives you a response between 0 and 1 again, and the curvature of that varies based on what value you set for inRoot.