Code:
require 'cairo'
--[[AUDIO SPECTRUM ANALYSER by wlourf 10 March 2010 http://u-scripts.blogspot.com/
It use some librairies and some portions of code from Impulse Screenlet for gnome.
Thanks to Ian Halpern http://impulse.ian-halpern.com/ for this ;-)
Requirements
libfftw3-3 libpulse0
You also need impulse.so and libimpulse.so from Impulse project (they are in the equalizer.tar.gz archive)
Save them in this script folder.
(The impulse library creates a pulse audio connection context that reads the output stream from pulseaudio
in a thread natively which can then be read from python.
You can specify impulse to either output the raw stream or output the fft of the raw stream.)
v1.0 21 Feb. 2010
v1.1 28 Feb. 2010 integration of Bargraph widget + use of a pipe for the python script
v1.2 10 March 2010 integration of Bargraph widget 1.3 with circular widget
the conky can be call with absolute path : conky -c /path/conkyrc
]]
--[[ BARGRAPH WIDGET
v1.3 by wlourf (03 march 2010)
This widget draw a simple bar like (old) equalizers on hi-fi systems.
http://u-scripts.blogspot.com/
The arguments are :
- "name" is the type of stat to display; you can choose from 'cpu', 'memperc', 'fs_used_perc', 'battery_used_perc'...
or you can set it to "" if you want to display a numeric value with arg=numeric_value
- "arg" is the argument to the stat type, e.g. if in Conky you would write ${cpu cpu0}, 'cpu0' would be the argument.
If you would not use an argument in the Conky variable, use ''.
- "max" is the maximum value of the bar. If the Conky variable outputs a percentage, use 100.
- "nb_blocks" is the umber of block to draw
- "cap" id the cap of a block, possibles values are CAIRO_LINE_CAP_ROUND , CAIRO_LINE_CAP_SQUARE or CAIRO_LINE_CAP_BUTT
see http://www.cairographics.org/samples/set_line_cap/
- "xb" and "yb" are the coordinates of the bottom left point of the bar, or the center of the circle if radius>0
- "w" and "h" are the width and the height of a block (without caps), w has no effect for "circle" bar
- "space" is the space betwwen two blocks, can be null or negative
- "bgc" and "bga" are background colors and alpha when the block is not LIGHT OFF
- "fgc" and "fga" are foreground colors and alpha when the block is not LIGHT ON
- "alc" and "ala" are foreground colors and alpha when the block is not LIGHT ON and ALARM ON
- "alarm" is the value where blocks LIGHT ON are in a different color (values from 0 to 100)
- "led_effect" true or false : to show a block with a led effect
- "led_alpha" alpha of the center of the led (values from 0 to 1)
- "smooth" true or false : colors in the bar has a smooth effect
- "mid_color",mid_alpha" : colors of the center of the bar (mid_color can to be set to nil)
- "rotation" : angle of rotation of the bar (values are 0 to 360 degrees). 0 = vertical bar, 90 = horizontal bar
- "radius" : draw the bar on a circle (it's no more a circle, radius = 0 to keep bars)
- "angle_bar" : if radius>0 angle_bar is the angle of the bar
v1.0 (10 Feb. 2010) original release
v1.1 (13 Feb. 2010) numeric values can be passed instead conky stats with parameters name="", arg = numeric_value
v1.2 (28 Feb. 2010) just renamed the widget to bargraph
v1.3 (03 March 2010) added parameters radius & angle_bar to draw the bar in a circular way
]]
function bar_graph(name, arg, max, nb_blocks, cap, xb, yb, w, h, space, bgc, bga, fgc, fga,alc,ala,alarm,led_effect,led_alpha,smooth,mid_color,mid_alpha,rotation,radius, angle_bar)
local function rgb_to_r_g_b(colour, alpha)
return ((colour / 0x10000) % 0x100) / 255., ((colour / 0x100) % 0x100) / 255., (colour % 0x100) / 255., alpha
end
local function setup_bar_graph()
local value = 0
if name ~="" then
local str = conky_parse(string.format('${%s %s}', name, arg))
value = tonumber(str)
else
value = arg
end
if value==nil then value =0 end
local pct = 100*value/max
local pcb = 100/nb_blocks
cairo_set_line_width (cr, h)
cairo_set_line_cap (cr, cap)
local angle_rot= rotation*math.pi/180
local alpha_bar = (angle_bar*math.pi/180)/2
for pt = 1,nb_blocks do
local light_on=false
--set colors
local col,alpha = bgc,bga
if pct>=(100/nb_blocks/2) then --start after an half bloc
if pct>=(pcb*(pt-1)) then
light_on=true
col,alpha = fgc,fga
if pct>=alarm and (pcb*pt)>alarm then col,alpha = alc,ala end
end
end
--vertical points
local x1=xb
local y1=yb-pt*(h+space)
local radius0 = yb-y1
local x2=xb+radius0*math.sin(angle_rot)
local y2=yb-radius0*math.cos(angle_rot)
--line on angle_rot
local a1=(y2-yb)/(x2-xb)
local b1=y2-a1*x2
--line perpendicular to angle_rot
local a2=-1/a1
local b2=y2-a2*x2
--dots on perpendicular
local xx0,xx1,yy0,yy1=0,0,0,0
if rotation == 90 or rotation == 270 then
xx0,xx1=x2,x2
yy0=yb
yy1=yb+w
else
xx0,xx1=x2,x2+w*math.cos(angle_rot)
yy0=xx0*a2+b2
yy1=xx1*a2+b2
end
local xc,yc
--perpendicular segment
if alpha_bar == 0 then
cairo_move_to (cr, xx0 ,yy0)
cairo_line_to (cr, xx1 ,yy1)
xc,yc=(xx0+xx1)/2,(yy0+yy1)/2
else
cairo_arc( cr,
xb,
yb,
radius+(h+space)*(pt)-h/2,
( -alpha_bar -math.pi/2+angle_rot) ,
( alpha_bar -math.pi/2+angle_rot)
)
xc=xb+ (radius+(h+space)*(pt))*math.sin(angle_rot)
yc=yb- (radius+(h+space)*(pt))*math.cos(angle_rot)
end
--colors
if light_on and led_effect then
local pat = cairo_pattern_create_radial (xc, yc, 0, xc,yc,w/1.5)
cairo_pattern_add_color_stop_rgba (pat, 0, rgb_to_r_g_b(col,led_alpha))
cairo_pattern_add_color_stop_rgba (pat, 1, rgb_to_r_g_b(col,alpha))
cairo_set_source (cr, pat)
cairo_pattern_destroy(pat)
else
cairo_set_source_rgba(cr, rgb_to_r_g_b(col,alpha))
end
if light_on and smooth then
local radius = (nb_blocks+1)*(h+space)
if pt==1 then
xc0,yc0=xc,yc --remember the center of first block
end
cairo_move_to(cr,xc0,yc0)
local pat = cairo_pattern_create_radial (xc0, yc0, 0, xc0,yc0,radius)
cairo_pattern_add_color_stop_rgba (pat, 0, rgb_to_r_g_b(fgc,fga))
cairo_pattern_add_color_stop_rgba (pat, 1, rgb_to_r_g_b(alc,ala))
if mid_color ~=nil then
cairo_pattern_add_color_stop_rgba (pat, 0.5,rgb_to_r_g_b(mid_color,mid_alpha))
end
cairo_set_source (cr, pat)
cairo_pattern_destroy(pat)
end
cairo_stroke (cr);
end
end
--prevent segmentation error
local updates=tonumber(conky_parse('${updates}'))
if updates> 3 then
setup_bar_graph()
end
end
-----------------------END OF BARGRAPH WIDGET --------------------
------------[[ two useful functions ]]------------------
function string:split(delimiter)
local result = { }
local from = 1
local delim_from, delim_to = string.find( self, delimiter, from )
while delim_from do
table.insert( result, string.sub( self, from , delim_from-1 ) )
from = delim_to + 1
delim_from, delim_to = string.find( self, delimiter, from )
end
table.insert( result, string.sub( self, from ) )
return result
end
function rgb_to_r_g_b(colour,alpha)
return {((colour / 0x10000)% 0x100) / 255., ((colour / 0x100) % 0x100) / 255., (colour % 0x100) / 255.,alpha}
end
------------[[ end of two useful functions ]]------------------
------------ HERE ARE THE THEMES -----------------------------
---------Function which call the bargraph widget
function draw_widget(x,y,audio_sample_array,n_cols,col_width,col_spacing, n_rows, row_height , row_spacing,radius)
local blocks = 20
local cap_round = CAIRO_LINE_CAP_ROUND
local cap_square = CAIRO_LINE_CAP_SQUARE
local cap_butt = CAIRO_LINE_CAP_BUTT
local w,h=col_width,row_height
local s=row_spacing
local bgc,bga = 0x666666, 0.5
local fgc,fga = 0x0000FF, 1
local alc,ala = 0xFF0000, 1
local mid_color,mid_alpha = 0xFFFF00, 1.0
local led_effect, led_alpha = true , 1.0
local alarm = 80
local smooth = true
local angle_rot = 0
local angle_bar=360/n_cols
freq = #audio_sample_array / n_cols
last_i=1
local col_n=0
for i=math.floor(freq),#audio_sample_array, freq do
i=math.floor(i)
--sometimes first col = 1, first col has to be set to 0 with the offset "col_min"
col = math.floor(i / freq)
if i==math.floor(freq) then col_min = col end
col=col-col_min
--sum all the frequencies in the range
local sum=0
local idx=0
for j=last_i,i do
if audio_sample_array[j] == nil then audio_sample_array[j]=0 end
sum=sum+audio_sample_array[j]
idx=idx+1
end
--sum = sum / (i-last_i+1)
if sum>1 then sum=1 end
last_i=i+1
rows = math.floor( sum * n_rows )
if rows > peak_heights[i] then
peak_heights[i] = rows
peak_acceleration[i] = 0
else
peak_acceleration[ i ] = peak_acceleration[ i ] + .1
peak_heights[ i ] = peak_heights[ i ] - peak_acceleration[ i ]
end
if peak_heights[ i ] < 0 then
peak_heights[ i ] = 0
end
local xb=x + col_n * ( w+col_spacing )
local yb=y -- h+row_spacing
--reminder bar_graph(name, arg, max, nb_blocks, cap, xb, yb, w, h, space, bgc, bga, fgc, fga,alc,ala,alarm,led_effect,led_alpha,
-- smooth,mid_color,mid_alpha,rotation,radius,angle_bar)
if radius==nil then radius=0 end
if radius==0 then
bar_graph('', rows, n_rows, blocks, cap_butt, xb, yb, w, h, row_spacing, bgc, bga, fgc, fga,alc,ala,alarm,led_effect,led_alpha,
smooth,mid_color,mid_alpha,angle_rot,0,0)
else
bar_graph('', rows, n_rows, blocks, cap_butt, x, y, w, h, row_spacing, bgc, bga, fgc, fga,alc,ala,alarm,led_effect,led_alpha,
smooth,mid_color,mid_alpha,angle_rot,radius,angle_bar)
angle_rot=angle_rot+angle_bar
end
col_n=col_n+1
end
end
--Function which draw the circle equalizer
function draw_circle_lcd(x,y,radius,audio_sample_array,n_cols,col_width,col_spacing, n_rows, row_height , row_spacing , bar_ca, peak_ca, radius)
freq = #audio_sample_array / n_cols
l = #audio_sample_array
last_i=1
cairo_set_line_width(cr, row_height)
for i=1, n_cols do
--sum all the frequencies in the range
local sum=0
for j=last_i,i*freq do
j=math.floor(j)
if audio_sample_array[j] == nil then audio_sample_array[j]=0 end
sum=sum+audio_sample_array[j]
end
last_i=i*freq+1
if sum>1 then sum=1 end
rows = math.floor( sum * n_rows )
cairo_set_source_rgba(cr, bar_ca[ 1 ], bar_ca[ 2 ], bar_ca[ 3 ], bar_ca[ 4 ] )
if rows > peak_heights[i] then
peak_heights[i] = rows
peak_acceleration[i] = 0
else
peak_acceleration[ i ] = peak_acceleration[ i ] + .1
peak_heights[ i ] = peak_heights[ i ] - peak_acceleration[ i ]
end
if peak_heights[ i ] < 0 then
peak_heights[ i ] = 0
end
--print (sum, n_rows, rows)
for row=0,rows do
cairo_arc( cr,
x,
y,
radius+(row_height+row_spacing)*(row+1),
( math.pi*2 / n_cols ) * ( i + col_spacing/col_width),
( math.pi*2 / n_cols ) * ( i + 1 )
)
cairo_stroke(cr)
end
cairo_set_source_rgba(cr, peak_ca[ 1 ], peak_ca[ 2 ], peak_ca[ 3 ], peak_ca[ 4 ] )
cairo_arc( cr,
x,
y,
radius+(row_height+row_spacing)*(1+peak_heights[ i ]) , --(rows+1),
( math.pi*2 / n_cols ) * ( i + col_spacing/col_width),
( math.pi*2 / n_cols ) * ( i + 1 )
)
cairo_stroke(cr)
end
end
---fucntion which draw a regular equalizer
function draw_default(x,y,audio_sample_array,n_cols,col_width,col_spacing, n_rows, row_height , row_spacing , bar_ca, peak_ca)
freq = #audio_sample_array / n_cols
last_i=1
for i=math.floor(freq),#audio_sample_array, freq do
i=math.floor(i)
--sometimes first col = 1, first col has to be set to 0 with the offset "col_min"
col = math.floor(i / freq)
if i==math.floor(freq) then col_min = col end
col=col-col_min
--sum all the frequencies in the range
local sum=0
local idx=0
for j=last_i,i do
if audio_sample_array[j] == nil then audio_sample_array[j]=0 end
sum=sum+audio_sample_array[j]
idx=idx+1
end
if sum>1 then sum=1 end
last_i=i+1
rows = math.floor( sum * n_rows )
cairo_set_source_rgba(cr, bar_ca[ 1 ], bar_ca[ 2 ], bar_ca[ 3 ], bar_ca[ 4 ] )
if rows > peak_heights[i] then
peak_heights[i] = rows
peak_acceleration[i] = 0
else
peak_acceleration[ i ] = peak_acceleration[ i ] + .1
peak_heights[ i ] = peak_heights[ i ] - peak_acceleration[ i ]
end
if peak_heights[ i ] < 0 then
peak_heights[ i ] = 0
end
for row=0,rows do
cairo_rectangle(cr,
x + col * ( col_width + col_spacing ),
y - row * ( row_height + row_spacing ),
col_width, -row_height
)
end
cairo_fill(cr )
cairo_set_source_rgba(cr, peak_ca[ 1 ], peak_ca[ 2 ], peak_ca[ 3 ], peak_ca[ 4 ] )
cairo_rectangle(cr,
x + col * ( col_width + col_spacing ),
y - peak_heights[ i ] * ( row_height + row_spacing ),
col_width, -row_height
)
cairo_fill(cr )
end
end
--End of 'themes'
--Main function here
function conky_spectrum(theme, x, y, fft, n_cols, col_width, col_spacing, n_rows, row_height, row_spacing, bar_color, bar_alpha, peak_color, peak_alpha,radius)
if conky_window == nil then return end
--this is a global variable
if impulse_pipe == nil then
local s=conky_config
local pos=(s:reverse(s)):find("/")
if pos==nil then pos=0 end
lua_script_dir= s:sub(1 ,#s-pos+1)
impulse_pipe = io.popen('python ' .. lua_script_dir .. 'impulse.py ' .. fft .. ' ' .. lua_script_dir, "r")
peak_heights = {}
peak_acceleration = {}
for i =1,255 do
peak_heights[i] = 0
peak_acceleration[i] = 0
end
end
local cs = cairo_xlib_surface_create(conky_window.display, conky_window.drawable, conky_window.visual, conky_window.width, conky_window.height)
cr = cairo_create(cs)
local impulse = impulse_pipe:read()
--print (impulse)
if impulse=="-1" then
print ("Argument missing")
else
if bar_color == "nil" then bar_color = nil end
if bar_alpha == "nil" then bar_alpha = nil end
if peak_color == "nil" then peak_color = nil end
if peak_alpha == "nil" then peak_alpha = nil end
if bar_color ~=nil and bar_alpha ~=nil then
bar_color_alpha= rgb_to_r_g_b("0x" .. bar_color,bar_alpha)
end
if peak_color ~=nil and peak_alpha ~=nil then
peak_color_alpha= rgb_to_r_g_b("0x" .. peak_color,peak_alpha)
end
impulse=string.sub(impulse, 2, -2)
local audio_sample_array = string.split(impulse,",")
if theme=="circle" then
draw_circle_lcd(x,y,radius,audio_sample_array,n_cols,col_width,col_spacing, n_rows, row_height ,
row_spacing , bar_color_alpha, peak_color_alpha,radius)
elseif theme=="widget" then
draw_widget (x,y,audio_sample_array,n_cols,col_width,col_spacing, n_rows, row_height , row_spacing, radius)
else
draw_default(x,y,audio_sample_array,n_cols,col_width,col_spacing, n_rows, row_height , row_spacing ,
bar_color_alpha, peak_color_alpha)
end
end
cairo_destroy(cr)
cairo_surface_destroy(cs)
end
Bookmarks