Repository navigation
Contracts
Glass reads these. A change here is its own migration.
The display reads the tap's ring, described under The tap below: the peak of each channel and the bank, a hop at a time. From those it makes what a theme expects. The level falls no faster than 500 ms from full scale to nothing, as the old scope had it, then goes through the conditioning below. The bank is the spectrum projected onto the bands the theme on show asked for (the Spectrum section below): up to 256 per channel over 20 Hz to 20 kHz on a log, mel or linear scale, a full-scale sine reading 1.0 in its band, with a peak hold per band that falls by half every quarter second, and an onset bit per band group (sub-bass, bass, mid, high) when a group rises after quiet. A theme of the previous engine gets its bars regrouped from the bank by the old logarithmic mapping of the two channels' average, each bar the energy of the bands whose centre falls in it, smoothed at 60 percent against the last value, and squeezed with a logarithm onto the theme's max.value. A ring that has not been written for half a second reads as silence, so the needles fall when the player stops; a ring gone for three seconds is looked for again once a second.
[current] frame.rate in the plugin's config/meter.txt. The UI allows 10 through 60. Missing file or key is 30. A frame lasts 1 / frame.rate: the display sleeps what is left of that after drawing.
frame.rate.governor in the same section, on unless it says False, lets the display step the rate down the ladder 60, 45, 30, 20, 15 when it cannot keep up. A rate given on the command line with --fps is exact: the governor is off for that run.
On a screen that is the display's own (no kiosk, or glass-evo holding it) the display draws 15 frames a second, or the set rate when that is lower, from three seconds after the player last played or the screen was last touched, and is back at its full rate with the next note or touch. Under a kiosk and on a remote the display does not slow down for a player standing still.
meter.folder is WIDTHxHEIGHT plus optional text. That is the frame size. screen.width and screen.height override it when both are set. The background file is bgr.filename in meters.txt, or screen.bgr when the meter sets one. The picture is copied at its own size and is not scaled.
[current] meter names one section of meters.txt, or is random, or a comma list of sections. random draws every section of the file once before starting over; a list cycles in order. The meter changes after random.meter.interval seconds (default 60), or with the next title when random.change.title is true, in which case the timer does not run. A change reloads the meter's pictures, fonts and text motion. meter.visible = False in a section that sets config.extend = True hides the needles.
meter.type in the meter section is circular or linear; channels is 2 (default) or 1. Every origin below is offset by meter.x and meter.y, and may lie off screen.
A circular meter turns the indicator.filename picture about an origin: left.origin.x/y and right.origin.x/y, or mono.origin.x/y for one channel showing the mono level. The needle turns about the point distance pixels below its picture's centre, and that point sits on the origin. The angle is start + (stop − start) x level, positive degrees to the left of vertical. left.start.angle, left.stop.angle, right.start.angle and right.stop.angle give a channel its own pair; when the shared start.angle and stop.angle are absent, the left pair stands in. left.needle.flip and right.needle.flip mirror that channel's picture before it turns. steps.per.degree is not used: the needle turns to the nearest half degree.
A linear meter shows the picture as a bar. Its steps are position.regular of step.width.regular pixels, then position.overload of step.width.overload; a level reaches step floor(level x (regular + overload + 1)), and the bar is that step's width in pixels, at least one. The channel origins are left.x/y and right.x/y, or mono.x/y for one channel. direction says which end of the picture shows and where it is anchored:
| Direction | Left channel | Right channel |
|---|---|---|
left-right (default) |
the first w columns, at the origin |
same |
right-left |
the last w columns, ending at the picture's right edge |
same |
bottom-top |
the last w rows, ending at the picture's bottom edge |
same |
top-bottom |
the first w rows, at the origin |
same |
edges-center |
the first w columns, at the origin |
the last w columns, ending at the origin when flip.right.x is set, else starting at it |
center-edges |
the last w columns, ending at the origin |
the first w columns, at the origin |
indicator.type = single draws the whole picture moved by w pixels along the direction instead. flip.left.x and flip.right.x mirror a channel's picture. meter.visible = False under config.extend draws no level at all.
A meter with config.extend = True and spectrum.visible = True shows the spectrum that spectrum.name names in the theme's spectrum file, in a box spectrum.size wide and high; a comma list names several, one box each in that order, the second and later spectrum.<n>.size when given, else spectrum.size. A section's channel (mean, left, right) says which channel of the bank the analyser draws in its single layout; the previous engine's bars take the mean. The plugin's config/spectrum.txt gives base.folder, spectrum.folder, size (the number of bars, default 20) and max.value (a raw bin of this value is a full bar, default 100) under [current]. The spectrum theme's spectrum.txt holds one section per spectrum. It is looked for under the meter theme's own folder name: in a templates_spectrum tree beside the tree the theme is in, then under base.folder; <base.folder>/<spectrum.folder> is used when neither has a spectrum.txt. spectrum.pos in the meter section is not read; the section's own position is.
A section may say what the tap measures for it: bins (1 to 256, the bands over 20 Hz to 20 kHz), channels (1 for the two channels' average, 2 for each), scale (log, the default, mel or linear) and window (the FFT's length in samples: 2048, 4096, 8192 or 16384, another value rounded up to the next and anything above capped; absent, 4096 up to 64 bands, 8192 up to 128, 16384 above). The player derives one demand from every section of the spectrum theme on show: the most bands, two channels when any section asks or none says (a section that draws one channel, channel = left or right, asks for two as well), the first scale named, the longest window named; a section that names some of the four and no bins counts as 256 bands; a spectrum theme that names none of these is measured at size rounded up to the next of 32, 64, 128 and 256, on the log scale, two channels. The plugin writes the demand as JSON ({"bins","channels","scale","window"}) to /dev/shm/glasstap.demand when it changes and the tap follows it within a second, measuring the stream anew into a new ring; without the file the tap measures 128 log bands of two channels from an 8192 window.
In a section, spectrum.x and spectrum.y place the box on screen, and everything is clipped to it. A fill is one of: color with r,g,b or r,g,b,a; gradient with (r,g,b[,a]),(r,g,b[,a]),..., the first colour at the bottom; image with a file in the spectrum folder; image.extended with a file stretched to the layer. bgr.type with bgr.color, bgr.gradient or bgr.filename is the background, a plain picture at its own size, and player.bgr or nothing draws none; fgr.filename is a picture over everything. The background and the foreground are centred in the box.
bar.type with bar.color, bar.gradient or bar.filename builds the bar picture at bar.width by bar.height; both picture kinds are stretched to it. A bar rises in bar.height / steps pixel steps: a raw bin scaled to the bar height is rounded up to a whole step. Bar n shows the bottom height rows of its picture at origin.x + n x (bar.width + bar.gap) inside the box, rising from the baseline origin.y. reflection.type with its colour, gradient or file builds a picture of the bar's size the same way, and the bar's reflection shows its top height rows from reflection.gap below the baseline. With topping.height and topping.step, a slice of the bar picture that tall stays a step above the top of a bar that drops and falls topping.step pixels a frame until the bar reaches it again; it is not drawn while it rests on the bar.
[data.source] in the plugin's config/meter.txt shapes the tap's levels before they are plotted, in this order: each raw value is scaled from volume.max.in.pipe to volume.max and multiplied by the gain, then truncated to a whole number; the gain is volume.gain.db (unity at 0) times the live value in dB read from the file volume.gain.db.source once a second, when set. mono.algorithm derives mono from the scaled pair: average (default) or maximum. stereo.algorithm combines each channel with its previous plotted value: new (default) takes the new value, average the mean of both, logarithm maps 20 · log10(new / previous) clamped to −20 ... 3 dB onto 0 ... 100. smooth.buffer.size (default 0) plots the mean of the last that many results, from a buffer that starts full of silence.
Fonts come from [current] in the plugin's config/meter.txt, one key per style: font.light, font.regular, font.bold, font.italic and font.digi. A value of builtin, or none, is the plugin's face of that style in the fonts directory under the plugin's home (PeppyFont Light, Regular, Bold and Italic; DSEG7Classic-Italic.ttf for the clock). For the four text styles an absolute path to a file that exists is that file (a font uploaded through the Manager), and anything else is a file name joined with font.path. font.digi is found the same way (from 0.8.42; before, it was used as written and not joined with font.path). An older configuration's use.system.fonts = False sets every style to the built-in face. A character the face lacks is drawn from the built-in regular face.
Each meter in meters.txt places its texts. A position is x,y or x,y,style; the top of the line sits at y. A font size is the em in pixels, as FreeType sizes a face for pygame; the baseline sits the face's ascent below the top, rounded up to a whole pixel, and the line is ascent to descent high, rounded up, so a line lands on the same rows as PeppyMeter's. A character the face lacks is taken from the built-in regular face at the same em, on the same baseline.
| Key | Text | Default style |
|---|---|---|
playinfo.title.pos |
Title | bold |
playinfo.artist.pos |
Artist, or Artist - Album when the meter has no album line |
light |
playinfo.album.pos |
Album on its own line | light |
playinfo.samplerate.pos |
samplerate bitdepth as the player reports them, or the bitrate when it sends neither. Aligned in its box (playinfo.samplerate.maxwidth, or one measured by -44.1 kHz 24 bit- where the meter sets playinfo.maxwidth and the line no width) by playinfo.samplerate.align, else as the meter's texts are; never scrolls |
light |
volume.value.pos |
The volume as a number, 0 to 100 | light |
time.remaining.pos |
mm:ss left in the track, red in the last ten seconds, absent when the source has no length |
digi |
time.elapsed.pos |
mm:ss played |
digi |
time.total.pos |
mm:ss of the track's length |
digi |
Colours are playinfo.title.color, playinfo.artist.color, playinfo.album.color, playinfo.samplerate.color and time.remaining.color, falling back to font.color, then white; the samplerate line takes playinfo.type.color first. time.elapsed.color and time.total.color fall back to the remaining colour. Sizes are font.size.light, font.size.regular, font.size.bold, font.size.italic and font.size.digi, with the player's defaults 30, 35, 40, the regular size, and 40. The style word italic uses font.italic, or PeppyFont-Italic.ttf under the plugin's fonts, and falls back to the regular face. playinfo.samplerate.maxwidth gives the samplerate line a box of its own.
The volume number takes bold, regular, italic (from 0.8.65) or digi as its style word and is light otherwise. volume.value.fontsize sets its size (default the style's), volume.value.color its colour (default font.color), volume.value.maxwidth its box, and volume.value.font names a font file of its own. From 0.8.65: volume.value.align (left, center, right; default the meter's alignment) places it in its maxwidth, volume.value.format puts words around the number with {} where it goes (VOL {} %; a pattern with no {} is none), and volume.value.mute is what stands in its place while the player is muted (default the number as ever). The Meters-Reference has the keys as a table.
The remaining time is the track's length minus the whole seconds played, truncated; 218.051 with 4.356 played shows 03:34. Position advances once a second between the player's reports while the status is play. Elapsed is the whole seconds played, total the whole seconds of length.
A time field's style word is digi or absent for the clock font, light or bold for those text fonts, and any other word for the regular font. In the clock style, time.<field>.font names a font file of its own, found as an absolute path, then inside the theme folder, then under font.path, and falling back to the clock font, and time.<field>.fontsize sets its size, default font.size.digi. In a text style the field uses that style's size. From 0.8.70 a text field (playinfo.title, artist, album, samplerate, ticker, next.title, next.artist, next.album) takes playinfo.<field>.font and playinfo.<field>.fontsize the same way: the file found as an absolute path, then inside the theme folder, then under font.path, falling back to the font and the size of the field's style.
While the player keeps the display up after a pause, it writes /tmp/glass_persist as seconds:start_ms:mode. With mode countdown and a status other than play, the remaining field shows the seconds left of that period in orange (242,165,0); with freeze or no file it shows the track time. The display reads the file at every frame; the browser face, which has no file system, takes the same from the channel's persist line. The plugin removes the file when the period ends and when the player plays again.
Under a kiosk the display leaves when the period ends. On a screen that is the display's own it stays: the period passes, with its countdown where that display is chosen, and then the theme stands still, its meters at rest, until the player plays again.
Every title, artist, album and next line has a box: its own playinfo.<field>.maxwidth, else playinfo.maxwidth, else the width left on screen from its position minus 20 pixels, or six tenths of the screen when the lines are centred. playinfo.align is left (default), center or right and places a text that fits; the legacy playinfo.center = True and playinfo.text.center = True mean centred. A text wider than its box moves: it runs to its end, pauses 400 ms, and comes back, drawn at the box's left edge minus its offset and clipped to the box.
Speed comes from the player's [current] scrolling.mode: default is 40 pixels a second everywhere; custom takes scrolling.speed.title, .artist and .album; otherwise the meter's playinfo.scrolling.speed.title, .artist, .album, then its playinfo.scrolling.speed, then 40.
playinfo.next.title.pos, playinfo.next.artist.pos and playinfo.next.album.pos show the track after the current one in the player's queue, each with its own colour and maxwidth, in the regular style unless the position says otherwise. The next track is taken from the queue the plugin pushes down the channel; a display that has been given no queue line asks the player for its queue when a state arrives. Either happens only when a meter uses these rows.
playinfo.ticker = True adds one line at playinfo.ticker.pos: artist, title and album, non-empty parts only, joined by playinfo.ticker.separator (default ·) with playinfo.ticker.space_between spaces on each side, then Next: Artist - Title when playinfo.ticker.append_next is set and a next track exists, then playinfo.ticker.end_spaces spaces (default 8). Three copies of that segment loop without a pause in playinfo.ticker.direction, rtl (default) or ltr, at playinfo.ticker.speed or the meter's scrolling speed. playinfo.ticker.maxwidth is always capped to the visible width from the position. playinfo.ticker.color defaults to the title colour. playinfo.ticker.replace = True hides the separate title, artist, album and next lines. Values are trimmed as the player's parser trims them, so spacing around the separator comes from space_between.
The player reports the art location in its state. A location that starts with / is a path the player serves on localhost:3000; anything else is fetched as given. The answer must be a picture: one the server calls an image in its Content-Type, or, from 0.8.31, one that begins as a JPEG, a PNG, a GIF or a WebP whatever the server calls it or without a word (the Squeezelite plugin's proxy passes a Lyrion server's covers on with no Content-Type). The Manager's route for the browser views tells a picture the same way and passes it on under its own type. intake fetches in the background with a three second limit and keeps the bytes under the temp directory, keyed by the location; a picture fetched earlier for the same location is reused.
The meter places the picture with albumart.pos as x,y and albumart.dimension as w,h. Both are required. The picture is decoded by content and stretched to w by h, drawn over the meter face and under the meter foreground. albumart.mask names a picture in the theme folder; it is stretched to the box and its white is cut away, its black kept. albumart.border is a width in pixels drawn inside the box in font.color.
A meter may show up to five pictures from the playing track's folder, folderlayer.1.* to folderlayer.5.*, plus the legacy folderlayer.* when folderlayer.enabled = True. A layer exists when it has pos (x,y) and dimension (w,h). files lists names to try in order, default back.png, Back.png, back.jpg, Back.jpg, logo.png, Logo.png; a name with a slash, a .., or an extension other than png, jpg, jpeg, gif or webp is skipped. The track's folder is its location as the player reports it, without a leading music-library/ or mnt/, under /mnt; the first file that exists is shown and the folder is looked up again only when the track's folder changes. A track in a cue sheet (cue://<path>@<track>) takes the sheet's folder. A source that is not a file shows nothing.
scale is fit (default: kept in proportion and centred in the box), stretch, or cover (the box filled, the picture's shape kept and the middle cut out). border is a width in pixels drawn inside the box in font.color, only when a picture is shown. zorder is overlay (default) or background.
A meter offers a fanart slot with fanart.pos (x,y) and fanart.dimension (w,h); fanart.scale is fit (default), stretch or cover (the box filled, the picture's shape kept and the middle cut out), fanart.zorder background (default) or overlay. Whether anything shows is the player's decision: Glass asks the player's glass_artistfanart endpoint (POST /api/v1/pluginEndpoint with the artist and the track location) and gets the picture references, the interval in milliseconds, the transition (none, fade, merge), its length, and the order (sequential, random). A reference is a path under /data/plugins; when that file is missing it is fetched through /albumart?sectionimage= instead.
The set is asked for when the artist changes, and asked again on every track change and whenever the interval has passed since the last advance; an answer with a different set restarts the show, an answer with the same set moves it one picture on. Sequential order takes the next picture round the set; random order takes any other picture. The starting picture and the time of the last advance are remembered per artist and set for as long as the display runs, twenty sets at most, and a new artist with no memory starts at the first picture, or at random in random order. From 0.8.26 the show is the artist's, not the meter's: a change of meter carries it on where it was, a meter with no place for fanart rests it, and the next that has one takes it up again; before, every change of meter started the show anew. The first picture of a new set fades or merges in from what is below.
A merge crossfades the old picture out and the new one in over the transition length. A fade takes the old one out over the first half and the new one in over the second, or the new one in alone when there is no old picture. The length is at least 50 ms.
rotation.quality in the player's [current] is low, medium (default), high or custom. custom applies rotation.speed as a multiplier to every turning speed; the presets multiply by one. From 0.8.57 the quality paces the turning, as it did under PeppyMeter Screensaver: a record, a reel and turning art are drawn anew low 4, medium 8, high 15 times a second, in steps of 12, 6 and 3 degrees, and under custom at rotation.fps times a second in steps of 45 over that number (between 1 and 12); between two of those moments the picture stands as last drawn and is not painted again. A scene carries the pace as turn_pace. Before 0.8.57 the redraw rates were read and not applied, and a picture turned at every frame. reel.direction is the default turning direction, cw or ccw (default).
A record is vinyl.filename with vinyl.center, the point it turns about; vinyl.pos is kept for themes that set it; vinyl.dimension stretches the picture first; vinyl.direction overrides the default. vinyl.filename is a theme picture, or album,theme to prefer the file album from the playing track's folder with theme as the fallback (,theme is the theme alone). The record turns at albumart.rotation.speed turns a minute while the player plays, while a stop is only a transition, and while the tonearm moves; when playback stops it slows to a halt over the tonearm's lift time (a second and a half without a tonearm) with an ease-out. A meter with a tonearm and no record but a reel turns that reel as the record, at reel.rotation.speed when the art speed is not set.
albumart.rotation = True turns the album art with the record, on the record's centre (its own centre when there is no record), at albumart.rotation.speed. Turning art is cut to the ellipse of its box when it has no albumart.mask; a spindle of radius 5 and a ring of radius a tenth of the box (at least 3) are drawn on the centre in font.color, and albumart.border becomes a round border.
A tonearm is tonearm.filename with tonearm.pivot.image (the pivot inside the picture) placed on tonearm.pivot.screen; the picture is drawn unturned at 0 degrees and positive angles turn it counter-clockwise, so -90 points down. It rests at tonearm.angle.rest, drops onto the record over tonearm.drop.duration seconds to the angle the track's progress gives between tonearm.angle.start and tonearm.angle.end, follows the progress, and lifts back over tonearm.lift.duration when playback stops, when less than a second and a half remains, or when the progress jumps by more than two degrees, in which case it drops again where the track now is. Moves ease out. Progress is the position over the track's length; a source without a length drops the arm to tonearm.angle.start and leaves it there while it plays.
A meter with a reel centre and no tonearm or record is a cassette. reel.left.filename and reel.right.filename are theme pictures, or album,theme to prefer the file album from the playing track's folder, scaled to the theme picture's size, with theme as the fallback; reel.left.center and reel.right.center are the points they turn about, and reel.left.pos and reel.right.pos are kept for themes that set them. Both reels turn at reel.rotation.speed turns a minute while the player plays or a stop is only a transition, in reel.direction from the meter, else from the player's [current], default ccw. Under rotation.quality = custom, spool.left.speed and spool.right.speed from the player scale each reel; the presets scale by one.
spool.adaptive in the meter, else in the player's [current], makes the reels follow the tape: with counter-clockwise reels the left one is the supply, turning at half speed when the tape starts and one and a half times at the end, and the right one the take-up, the other way round; clockwise reels swap the roles. The factor moves with the playback progress.
queue.mode = queue in the player's [current] makes progress run over the whole queue: the lengths of the tracks before the playing one plus its position, over the sum of every track's length, with the time left measured to the end of the queue. The lengths come from the queue line of the channel; a display that has been given none asks the player for its queue every ten seconds. Track progress applies when the queue is empty or has no length, when the playing track has no length, or when the source is a stream (volatile). Reels and the tonearm follow this progress.
All indicators need config.extend = True in the meter. Each has a *.pos.
mute, shuffle, repeat and playstate show one state. With *.led (w,h) the state is a shape, *.led.shape circle (default) or rect, in the colour its state takes from *.led.color, a list of r,g,b triples in state order: mute is off, muted, zero volume; play state is stop, pause, play; repeat is off, all, single, and a fourth triple adds infinity; shuffle is off, on, with a third legacy infinity, and six values are read as on then off. Without *.led, *.icon lists one picture per state in the theme folder, an empty name being a state with no picture. A state past the last takes the last. *.led.glow or *.icon.glow is a blur radius in pixels; a glow is the shape, or the picture's own alpha, in *.led.glow.color or *.icon.glow.color per state (the LED colour, or white behind a picture, when absent) at *.led.glow.intensity or *.icon.glow.intensity (0 to 1, default 0.5), blurred by the radius and drawn behind; the canvas grows by twice the radius on every side and *.pos is its top left. Mute is muted when the player says so, zero when the volume is 0, else off; shuffle follows the player's random flag; repeat is single before all; infinity playback, which the plugin's channel reports, lands on the repeat indicator's fourth state when the theme has one, else on the shuffle indicator's third; without a channel it is never shown.
volume and progress are gauges of a value from 0 to 100. *.style is numeric (volume default), slider (progress default), knob or arc. numeric writes 42% at *.pos in the regular font at *.font.size (default 24) in *.color. A slider without *.slider.tip is a bar in the *.dim box, *.bg.color under *.color, filled left to right or bottom to top when *.slider.orientation is vertical (or, for another word, when the box is taller than wide); *.fill.radius rounds its ends; progress.border draws a border in progress.border.color. A slider with *.slider.tip draws *.slider.track at the position, then the tip at *.slider.travel (start,end pixels; 100 % at start and 0 % at end when vertical, 0 % at start and 100 % at end when horizontal, default the box less the tip), centred across the box, moved by *.slider.tip.offset; *.fill.color draws a tail from the zero end to the tip's centre, *.fill.width thick (default the tip's size), moved by *.fill.offset and rounded by *.fill.radius. A knob turns *.knob.image about the box's centre from *.knob.angle.start at 0 to *.knob.angle.end at 100, degrees counter-clockwise from the right. An arc fills the ring *.arc.width inside the box's ellipse from *.arc.angle.end round to *.arc.angle.start in *.bg.color, and from the value's angle round to *.arc.angle.start in *.color. On either gauge, *.marker.N.pos (percent) with .image or .label and .fontsize (default *.font.size) mark points along the bar or arc, numbered from 1 until the first missing position, ten at most; *.head.image moved by *.head.offset sits at the value. The volume gauge's volume.dim defaults to 100,20; the progress gauge needs its progress.dim.
touch.interactive in the player's [current] is theme (default), on or off. A meter's controls act when the value is on, or when it is theme and the meter sets interactive = True or has buttons; never when it is off. The controls are the meter's buttons, button.<name>.pos with image (a picture from the theme folder, drawn after the indicators, its size the button's; a second picture in the list draws instead while the button is active, by its action and the player's state, or while a finger is on it; with three pictures or more the list is one picture per state of the action's indicator, for repeat off, all, single, infinity, for mute off, muted, zero, for random off, on, and for play, pause, stop and toggle the play state as stop, pause, play) or size, and action (toggle, play, pause, stop, next, previous, meter.next, meter.previous, mute, random, repeat, dismiss), then the play state, mute, shuffle and repeat indicators (a LED's box its size, a picture's the picture's in its state), then the volume and progress gauges (their *.dim box). A pointer event arrives in the frame's pixels: a fitted window's are scaled by the window, an unfitted one's have the frame's offset taken off. A tap is the first control whose drawn box holds the lift point, buttons before indicators, else the nearest whose grown box does: every box grown to at least twice touch.margin (default 24) on each axis, centred, a gauge's long axis reaching half a margin past each end. A tap sends the channel command the control names: toggle for the play state, volume toggle for mute, random with the state flipped, repeat with the next of off, all, single; a gauge tap sends volume at the fraction along it (left to right when wider than tall, bottom to top otherwise, clamped) or seek at the fraction of the track's length. A finger down on a gauge's region drags it until it lifts: the scene's volume or progress shows the finger's value meanwhile, volume is sent at most every 150 ms while moving and at the lift, seek at the lift only. meter.next and meter.previous step the rotation at the next frame (a list steps back; a random rotation draws another); dismiss does what exit.on.touch does, and nothing on a screen that is the display's own or on a remote that carries a face. A touch no control took follows exit.on.touch under a kiosk; on a screen that is the display's own it does nothing; on a remote it plays or pauses, unless the remote carries a face, where it does nothing either.
The pipeline compiled to WebAssembly (crates/page; bins/glass-face is the module, one line over page::exports!) draws the meters in a browser page from the same configuration, theme, fonts and icons a remote display brings, and the same hops the remotes receive. The module has no file system and no clock: the page puts every file into a table under /glass, by the path a remote's home would give it, and the page's clock comes in with every frame. The exports, in the order the page calls them: configure with the manager's /api/remote/config answer (the two configuration files go into the table, pointed into /glass; the fonts and icons to fetch come back as a plan of path, route and checksum); theme_files with the /api/themes/<theme>/files answer (the theme's files as a plan); put_file for every file of both plans; start with the meter to show, empty for the configuration's, which reads the theme as the display reads it; then hop for every frames datagram, event for every line of the plugin's, and frame at the theme's rate, which answers with the RGBA bytes of frame_width by frame_height. frame_rate is the configuration's rate. pointer takes a finger or mouse event in the frame's pixels (0 down, 1 move, 2 up) against the meter's controls, the same regions, taps and drags as on the player's screen (the section on controls above), and returns how many things it asked for, the answer a JSON array of them: {"command":{"name","value"}}, which the page posts to /api/face/command; {"meter":"<name>"} when a button stepped the rotation, the module having put that meter on show; {"dismiss":true}, which leaves full screen. The controls act when the player's setting says so, as for the player's own display. The pictures the module cannot fetch for itself come through the page: wants answers with what the meter on show is waiting for, as a JSON array, {"kind":"file","path","url"} for a picture to fetch at url (the manager's /api/face/picture for the album art and the fanart, /api/remote/track-file for a picture of the track's folder) and hand in with put_file under path, or to mark with missing when the manager answers 404, and {"kind":"fanart","artist","uri"} for the artist's set, answered with fanart_answer and the manager's /api/face/fanart JSON. A want is listed again at every wants until it is met; the page asks after each frame it paints, at most every quarter second and only while it is visible, fetches each once, and after a failed fetch (other than a 404) asks again ten seconds later; a fanart set that cannot be fetched is answered as none. configure, theme_files and start return 1 with the error as the answer, start otherwise answering with the meter now on show; event returns 1 for a line the display does not read; frame returns null before start. Strings and bytes cross through alloc and free; an answer is read at answer_ptr for answer_len bytes, until the next call that answers. The frames reach the page as an event stream: the daemon serves its datagrams on a local socket (glass-serve --face), one data: line per datagram in base64 and a comment after ten seconds of silence, and the manager proxies that at /api/face/events as hop events, adds the plugin's lines as plugin events (the last config, the state as it stands now, showing, infinity, queue, persist and views first), and says with feed events whether frames flow. On the player the daemon runs whenever the plugin does; the port opens only while remote displays are served (--local otherwise). What /api/remote/config answers, its face block among it, and the wire the hops travel on are on the Remotes page.
From 0.8.9 a module may carry a face over the theme, written against crates/overlay, the contract a face on the player's own screen is written against (the section on the overlay contract below): the pipeline asks it what it covers, hands it a copy of the frame to draw on, and offers it every pointer event before the theme's controls. glass-evo's module (glass-evo-face.wasm, in its component from 0.1.14) is the pipeline with glass-evo's face. Three exports serve a face, and do nothing in a module without one: zone(offset_minutes, name_ptr, name_len), the page's minutes east of universal time and its zone's short name, told at the start and once a minute, from which the face's time of day (View::wall) is reckoned with the frame's clock; taken(), what the face asked of the player at a frame, answered in pointer's form and asked after every frame; and overlaid(), 1 where the module carries a face. A face reads its theme from the table under faces/<Name>/face.txt, where the page puts the text the Manager's GET /api/face/module gives.
crates/overlay is the contract between the display and a face drawn over it: what a face is asked, what it is shown of the display, and the types it is written against. It knows no window and no file system, so a face written against it draws the same on the player's screen and on a remote, where the display's loop calls it, and in a browser, where the page's pipeline does. glass:: offers Overlay, View, Cover, Wall and the face module under the same names, and glass::run_with(args, face) is the display with a face over it, with the same arguments and environment as the display alone. With no face nothing changes.
A face implements Overlay:
| Method | What it is asked |
|---|---|
covers(view) -> Cover |
What the face has to draw over the frame about to be shown. Asked before every draw. A face that does not say answers Cover::New. |
draw(frame, view) -> bool |
To draw over the frame, after the theme. true when anything was drawn, and the whole picture is then shown again. |
pointer(kind, x, y, view) -> bool |
A pointer event in picture pixels (PointerKind: down, move, up), offered before the theme's controls. true when taken, and the theme's controls and the touch rules then do not see it. |
commands() -> Vec<Command> |
The commands for the player the face wants sent, taken every frame and sent the way the theme's own buttons send theirs. |
name() -> Option<String> |
What the face is called and its version, as a remote says it on its settings page and in its hello. None unless the face says. |
origin() -> Option<Origin> |
Where the display built with this face is released (from 0.8.53): an Origin of repository (owner/name on GitHub), asset (what its archives are called before the version), binary (its name inside an archive) and version. A remote display upgrades itself from there. None unless the face says, and a display whose face says none is offered no upgrade. |
Cover is the answer of covers:
| Value | Meaning |
|---|---|
Nothing |
Nothing at all. The face is not handed the frame, and the copy it would have drawn on is spared. |
Same |
What it drew on the frame before, were the picture under it the same. While the picture under it stands still the face is not asked to draw again, and the window is left as it is. |
New |
Something it has not drawn before. The frame is copied and the face draws on the copy. |
View is what a face is shown of the display at every call:
| Field | Holds |
|---|---|
input |
The player's state as the source has it, the cover's file among it once fetched. |
fonts |
The theme's fonts. |
width, height
|
The picture's size in pixels. |
now_ms |
The display's clock in milliseconds. It only goes forward, and only differences of it mean anything. |
wall |
The time of day where the player is, a Wall. |
ours |
Whether the picture is the display's to keep: a screen that is the display's own, a remote built with a face, a page with a face. |
scale |
How large the face draws, from face.size in [current]: 1 for normal (default), 1.4 for large, 2 for car. |
settings |
Every face.<name> key of [current] by its name without the prefix, as written. The display reads none of them but the size; their meaning is the face's. |
theme_dir |
The folder of the theme on show, empty where there is none (from 0.8.50). What a theme brings for a face lies there beside its meters.txt, read as the display reads the theme's own files: the file system on a player and a remote, the page's table in a browser. |
Wall is the time of day broken down: epoch_s (seconds since 1970 in universal time), year, month (1 to 12), day (1 to 31), hour, minute, second, weekday (0 for Sunday to 6 for Saturday), yearday (0 for the first of January), offset_minutes (minutes east of universal time) and zone (the zone's short name where it is known, else empty). Wall::now() reads the system: its local time and zone on a player, its local time on Windows, where the zone's name is not read, and universal time where the system's zone is not read. Wall::at(epoch_ms, offset_minutes, zone) reckons it from a moment and a zone, which is how a browser's pipeline makes it from the page's clock and the zone export.
The face module gathers the types a face is written against: PointerKind; Frame, Fonts, render_text, read_art, fit_art, blur and the ui module from the renderer; Command; Input, Metadata and TextStyle; and is_file and read_to_string, which read where the display reads its own files, the file system on a player and the page's file table in a browser.
The laying of a face over the picture is the crate's too, so the screen and the browser views do it alike. Laid::lay(face, base, base_same, view) asks the face what it covers and answers with a Lay: drew, the face is over the picture and Laid::frame() is what to show, and same, it is the frame before's pixel for pixel and nothing was drawn again. The copy the face draws on is kept from frame to frame. The theme stands under a face while the player stands still. Before 0.8.51 the crate also had stands_black(metadata) and Black, for a black picture once a persist countdown had run out; the plugin clears the countdown as it ends, so the black showed for an instant at most, and both are removed.
start.animation = True in the player's [current] fades the first frame in: the frame is covered in transition.color (black, default, or white) at transition.opacity percent (default 100) and the cover thins to nothing over transition.duration seconds (default 0.5); transition.type = none turns every fade off. Each later meter of a random or list rotation fades in the same way whether or not start.animation is set. The lock file glass_fade_lock in the temp directory is touched when a fade starts, and a fade is skipped while the file is younger than the duration plus one second, so a display restarted within that time does not fade twice. When the window closes after a fade in, the frame fades out under the same cover before Glass exits.
After every start and every meter change, the levels and the spectrum bars rise to their values over 0.7 seconds in ten equal steps, as the engine raises its full scale.
From the bottom: the screen picture, the meter face, the reels, the record, the album art, background folder layers, background fanart, the needles or bars, the spectrum, the texts and the time, the tonearm, the indicators, the type area, overlay folder layers, overlay fanart, and the meter foreground last.
playinfo.type.pos places the area. playinfo.type.dimension is its box; 1,1, zero or absent means no box, which only text mode accepts. playinfo.type.mode is icon, text or both; the meter's value wins over the player's [current] value, and the default is icon. playinfo.type.align is left, center or right inside a box, default center. playinfo.type.color colours the label and tints an SVG icon, and defaults to font.color. playinfo.type.fontsize sets the label size; absent, it is the samplerate size, or inside a box the smaller of that and 0.45 x height, at least 10. The label uses the samplerate font style. playinfo.type.label = samplerate writes the sample rate line as the label, and the format's name when the player sends none.
The key is the reported track type in lower case with spaces as underscores, dsf as dsd, cut at the first character outside a-z, 0-9 and _, then the aliases the player uses (webradio is radio, tidal_connect is tidal, and so on). The icon is format-icons/<key>.png then .svg in the theme folder, then <key>.svg in the plugin's format-icons, then Volumio's stock set, matched ignoring case. An SVG is rendered with resvg fitted inside the box and tinted with the type colour; a PNG keeps its colours. Without an icon the label is drawn: proper case for known services, upper case otherwise. In both mode the icon sits on the left in a square of the box's shorter side and the label three pixels to its right.
The Volumio plugin is the process that starts and stops the player binary. It lives in plugin/ in the glass repo and is packaged by scripts/package.sh; run_glass.sh sets up the display and starts glass with GLASS_HOME pointing at the plugin's directory. Under that home the binary reads config/meter.txt and config/spectrum.txt, and finds the clock font and the fallback face in fonts and the player's icon set in format-icons. GLASS_CONFIG names another meter configuration for a review; its spectrum configuration, fonts and icons are found from it the same way. GLASS_CHANNEL names the plugin's socket with the player's state; the section on the channel has its lines.
While a window is open, glass creates the run flag /tmp/glass_running (mode 777) as it starts and removes it as it leaves. The plugin stops the player by removing the flag: glass checks it twice a second and leaves within half a second, fading out first when it faded in. Under a kiosk, a finger or a mouse button lifted on the window ends the player when [current] sets exit.on.touch or stop.display.on.touch, unless it lands on one of the theme's controls while the controls are on (the section on controls above); before leaving it writes 1 into the file named by the GLASS_DISMISS_FILE environment variable, which the launcher sets, but only while the run flag still stands, so the plugin can tell a person's dismiss from its own stop and re-arm its timeout. Closing the window from outside ends the player without the marker.
The screen is the display's own when no kiosk uses it and the display draws on it itself through KMS/DRM, or when the X server it draws on was brought up for the display alone, which is how glass-evo holds a screen the kernel does not drive. The display takes the first from screen.driver = kmsdrm, from SDL_VIDEODRIVER=kmsdrm, or under screen.driver = auto from finding no display server named in its environment; the plugin says the second with GLASS_SCREEN_OURS=1.
On such a screen the plugin keeps the display up whether or not the player plays, and starts it again whenever it finds it gone. When the player stops or pauses, the display stays: the persist time passes, with its countdown where that display is chosen, and then the theme stands still, its meters at rest, until the player plays again. The plugin does not remove the run flag when the period ends. A touch never ends the display, and one outside a control does nothing. The display turns the picture and the touch itself by screen.rotation, and draws at the standing rate while the player stands still (the section on the frame rate above). When a kiosk comes to want a screen the display draws on itself, the plugin removes the run flag and the display steps aside.
Beside the frame rate, the fonts and the touch keys named above, the display reads these from [current] of the meter configuration:
| Key | Values | Meaning |
|---|---|---|
position.type |
center (default), fit, or any other value |
center centres the frame in the window; fit scales the theme to the screen, its shape kept, and centres it; any other value puts the frame's top left at position.x, position.y. |
position.x, position.y
|
pixels, default 0 | The top left of an uncentred frame. Either may be negative. |
position.fit |
True or False (default) |
Scales the theme to the screen, its shape kept, whatever the position type. |
screen.driver |
auto (default), x11, wayland, kmsdrm (also kms, drm) |
What draws the window. Under auto the display takes what the launcher names. |
screen.rotation |
0 (default), 90, 180, 270
|
The picture and the touch turned clockwise, on a screen that is the display's own. Under a kiosk the X server turns the screen and the key is not applied. |
screen.pointer.shown |
True or False (default) |
Whether the pointer is drawn on the window. The plugin resolves it from the Screen tab's choice and what the player has. |
touch.matrix |
six numbers a,b,c,d,e,f
|
Maps a finger's share of the panel (x, y) to (ax + by + c, dx + ey + f) before it becomes a pixel. Anything else, or a matrix that maps everything to one point, is the identity. |
face.size |
normal (default), large, car
|
How large a face draws: 1, 1.4 or 2. |
face.<name> |
as written | A face's own settings, handed to the face unread (the section on the overlay contract above). |
| Variable | Set by | Meaning |
|---|---|---|
GLASS_HOME |
the plugin and the launcher | The plugin's directory. |
GLASS_CONFIG |
a person, for a review | Another meter configuration. |
GLASS_CHANNEL |
the plugin and the launcher | The plugin's socket. |
GLASS_DISMISS_FILE |
the plugin and the launcher | The file a person's dismiss is marked in. |
DISPLAY |
the plugin | The X display the settings name. |
SDL_VIDEODRIVER |
the plugin | What the display draws on by what the player has now: x11 while an X server is up, kmsdrm while no kiosk uses the screen and a panel is connected; left out otherwise. The launcher sets x11 itself when it finds the X server's socket. |
GLASS_SCREEN_OURS |
the plugin |
1 where the X server is there for the display alone. |
GLASS_BIN |
the plugin | The binary the launcher starts in the display's place: glass-evo's, when glass-evo owns the screen and its component is installed. |
GLASS_FACES |
the plugin | The folders face themes are kept in, the user's before the ones glass-evo ships, separated by a colon. |
GLASS_LOG, GLASS_LOG_TARGETS
|
the plugin | The log level and the targets (the Logging page). |
GLASS_GRAB, GLASS_GRAB_AFTER
|
a person |
GLASS_GRAB=PATH writes the window's pixels to that file once, after thirty frames or after as many as GLASS_GRAB_AFTER says, so a screen with no X server can be looked at from a terminal. |
Skins and fonts stay files. SkinDesc names them. expose opens the image files.
glasstap is an ALSA PCM plugin: the player's ALSA chain names it as a PCM of type glasstap with a slave (and pcm_type.glasstap { lib ... } pointing at the library), and every stream opened on it passes through to the slave byte for byte, in the format the player and the slave agreed, so PCM, DoP and native DSD arrive bit-perfect. Inside the player's process the audio thread copies each period across a lock-free relay, marked with the time it arrived, and a thread of the tap's own measures the samples a hop at a time, 1024 frames by default, and publishes each hop into a shared ring when its audio is heard: a period written at once is heard over the period's length after the frames ahead of it, which the tap learns by asking its PCM how many frames stand between the last one written and the sound; the buffer and period the player asked for are the fallback when the PCM cannot say. So the ring fills evenly and in step with the sound whatever a player's period is. What it publishes is: linear audio (8 to 32 bits, three-byte words, floats) as peak, RMS and spectrum from the top sixteen bits of each sample; DoP (told by its markers) and native DSD (DSD_U8, DSD_U16, DSD_U32) as a density level, with an empty spectrum. A format the tap does not read passes unmeasured. The library also offers the same measuring as an ALSA scope (pcm_scope_type.glasstap) for a meter PCM, with the FIFO keys below. scripts/tap_exact.sh plays raw frames of each format through the PCM into a file and proves the bytes.
The ring is a file /dev/shm/glasstap.<tag>.<pid>.<n>, one per writer (n counts the writers a process made); a reader takes the one written most recently and treats one not written for three seconds as left behind by a player that stopped. A writer removes its file when its stream closes, and makes a new one when the demand changes. Every new writer first removes the rings of writers whose process is gone. All values are little-endian. A slot's size is rounded up to a multiple of 64 bytes. The header is 256 bytes: the magic GLASSTAP (8), version (u32, 2), header size (u32), sample rate (u32), channels (u32, 1 or 2), the FFT window (u32), bands per channel (u32), slot count (u32), slot size (u32), writer pid (u32), hop (u32), then at offset 48 the sequence of the last slot published (u64), at 56 the monotonic time it was published in nanoseconds (u64), and at 64 the scale (u32: 0 log, 1 mel, 2 linear); a writer stores the sequence and the time last. Slot seq lives at 256 + (seq mod slots) * slot size and holds: the sequence (u64), the time (u64), the frames seen so far (u64), the peak of each of two channels (f32, linear, 1.0 full scale), the RMS of each (f32), the bank of each (bands f32, a full-scale sine reading 1.0 in its band), the peak hold of each (bands f32), the sequence again, and after it the frame's flags (u32: the onset bits in the low nibble, bit 4 for one-bit audio); a slot of an earlier writer has zeros there, or no room, which reads as none. The head sequence carries its top bit while the slot is being written; a reader whose head, tail and expected sequence disagree caught a write in flight and reads again. A mono stream, or a one-channel demand, is published as two equal channels. A DSD-over-PCM stream is measured by bit density and published with an empty bank. A reader also takes a ring of version 1, from a tap loaded before the bank into an audio process that has not restarted since: its raw spectrum (the amplitude at each FFT bin from 0 Hz to half the rate; its scale reads as log) is projected onto the default bank by the display and by the frames daemon, with a projector kept per rate and window whose hold decays as if the hop were half the window, so the meters and the remotes read on until the process restarts and loads the tap of this release. The bank is the bank crate's: a Hann-windowed FFT of the last window samples every hop, projected onto the bands' edges by power sum, scaled so a full-scale sine inside a band reads one, adjacent bands that would read the same bins told apart by splitting the range or by triangular weights, bin 0 never read.
The PCM's and the scope's configuration keys are hop (default 1024, the bank's), slots (default 64) and ring (the tag in the file name, default glasstap); fft_size is accepted and no longer read, the window being the demand's; the scope also takes, for the two FIFOs peppyalsa wrote, meter and spectrum (paths; none by default), meter_max, spectrum_max, spectrum_size, decay_ms, logarithmic_frequency, logarithmic_amplitude and smoothing_factor with peppyalsa's meanings. The FIFO records are the same as peppyalsa's: two little-endian u16 levels, and spectrum_size little-endian i32 values. tapdump [seconds] prints the live ring.
The plugin serves a local socket, /tmp/glass_channel, and the launcher names it to the display in GLASS_CHANNEL; without the variable the display looks at that path, and without the socket it asks the player itself over HTTP once a second. Each line on the socket is one JSON object in UTF-8, ended by a newline. The same channel is served over TCP for remote displays; the Remotes page has the port.
A display that connects hears hello, then state, queue, showing and infinity, in that order, each when the plugin holds one. The plugin then asks the player for its state, its queue and infinity playback afresh, and sends config to every display. Every later change follows as it happens, and the display moves the position on from the last state's seek by the time since. The next-track rows and the queue's progress are drawn from the queue line; a display that has not been given one asks the player for the queue itself. Lines of other kinds are ignored, so the plugin may say more than a display reads.
From the plugin:
| Line | Meaning |
|---|---|
{"kind":"hello","protocol":1,"plugin":"<version>"} |
The greeting: the protocol the plugin speaks and its version. |
{"kind":"state","state":{...}} |
The player's state as the player pushed it (the object the player answers getState with). For a display that connects, its seek is moved on by the time since the state arrived, while the player plays and never past the track's length. |
{"kind":"queue","items":[...]} |
The player's queue, one item per track with title, artist, album and duration in seconds. |
{"kind":"showing","theme":"<theme>","meter":"<meter>"} |
The meter the player's own display shows. It goes to remotes, which follow it. |
{"kind":"show","meter":"<meter>"} |
From 0.8.71, to the player's own display only: a meter it is asked to show (a meter.next button pressed in a browser view). It shows it when the theme on show has a meter of that name, and answers with its showing. |
{"kind":"infinity","on":true} |
Whether infinity playback is on, true or false. |
{"kind":"config","version":"<version>","theme":"<theme>","meter":"<selection>"} |
The configuration as remotes follow it: its version, the theme on show and the meter selection. Sent when a display connects and at every change; a remote starts its session again at another version. |
{"kind":"persist","mode":"<mode>","seconds":30,"startedAt":<ms>,"stays":false} |
The persist period, for a display that cannot read the persist file: the mode (countdown or freeze, empty when the period is cleared), its length, when it began in milliseconds since 1970, and stays, true where the display is still on the screen when the period is over, a screen that is the display's own. |
{"kind":"calibrate","points":5} |
Asks the display to calibrate its touch panel: it shows that many targets (three to nine, five when not said), reads the fingers and answers with the command calibration. |
{"kind":"views","face":true} |
For the browser pages: whether they carry glass-evo's face over the theme. A display does not read it. |
From a display:
| Line | Meaning |
|---|---|
{"kind":"command","name":"<name>","value":<value>} |
A command, with a value where it takes one: play, pause, toggle, stop, next, previous, seek (seconds), volume (0 to 100, or +, -, mute, unmute, toggle), random (true or false) and repeat (off, all, single). The plugin runs it through the player, and the state that follows comes back down the channel. |
{"kind":"command","name":"calibration","value":{...}} |
The answer to calibrate, which is not a command for the player: matrix (six numbers), error_px and samples for a map that fits, or error (timeout, or unfit with error_px) for none. The plugin keeps a matrix as the calibrated one and puts it to use. |
{"kind":"showing","theme":"<theme>","meter":"<meter>","rate":30} |
From the player's own display: the meter it shows and the rate it draws at, sent at its start, at every change of meter and when the governor changes the rate. The plugin passes the theme and the meter on to remotes; from a remote the line is not taken. |
{"kind":"hello","remote":{...}} |
From a remote, once after connecting: who it is. The Remotes page has its fields. |
The plugin drops a line longer than 65536 characters, the display what it has buffered past one mebibyte without a newline. The socket file is created by the plugin's user, which the display runs as. When the plugin restarts, the display connects again within two seconds; while the socket is gone it asks the player as before.
Peppy OS, the rescaler, and the legacy bookworm meter and spectrum plugins are not part of Glass.
- Home
- Quick-Start
- Settings
- Manager
- Manager-API
- Screen
- Artwork
- Catalog
- Backups
- Remotes
- Anymote
- Performance
- Troubleshooting
- Logging
Themes
Developers
The Glass interface