Displaying the Episode Waveform

What’s a Waveform?

A waveform is a visual representation of an audio file, based on how the audio volume intensity changes over the time. Spreaker uses the waveform to display an episode’s playback progress on the website and the widget, and provides access to the waveform data points through the API.

Loading the Waveform Data Points

The waveform data consists of a JSON-encoded array of 870 float numbers ranging from 0 (lowest) to 1 (highest). Each Episode exports the property waveform_url, containing the URL of a gzipped JSON containing the episode’s waveform data points.

Example: get an episode’s waveform URL

curl -s https://api.spreaker.com/v2/episodes/550 | json -a response.episode.waveform_url

Example: get an episode’s waveform data points

curl --compressed https://d3770qakewhkht.cloudfront.net/episode_550.gz.json

The response body is a JSON object containing all waveform data points.

{
    "response": {
        "points": [0.47449, 0.936584, 0.383244, ..., 0.000393, 0]
    }
}

 Drawing waveform

Drawing the waveform is quite easy. Given H the height of your waveform, you should draw a vertical line for each data point, where each line should be maximum height H * data_point_value. The waveform lines should be anchored at the bottom of the waveform area.

Note: the widget’s waveform, embedded at the top of the page, draws 2 lines (data points) every 3 pixels. The natural total width of that waveform is 870 + (870 / 3) pixels = 1160 pixels. In order to fit the waveform in a different area, you should rescale the data points (see next section).

Rescaling Waveform Data Ppoints

Spreaker provides 870 data points for each waveform. However, there’re some cases where you need more / less data points. For sake of semplicity, we provide you a PHP implementation of an algorithm that rescale data points from 870 values to the number of values you need.

<?php

function _interpolate($points, $num_out_points)
{
    $num_in_points = count($points);

    // Edge cases
    if ($num_out_points == $num_in_points) {
        return $points;
    } else if ($num_out_points <= 0) {
        return array();
    } else if ($num_out_points == 1) {
        return array(round(array_sum($points) / $num_in_points, 6));
    } else if ($num_in_points == 1) {
        return array_fill(0, $num_out_points, round($points[0], 6));
    }

    $output = array_fill(0, $num_out_points, 0);

    // Stretch
    if ($num_out_points > $num_in_points)
    {
        $delta = $num_in_points  / $num_out_points;

        for ($i = 0; $i < $num_out_points; $i++) {
            $interValue = $i * $delta;
            $prev       = floor($interValue);
            $next       = $prev + 1;
            $d          = $interValue - $prev;

            // taking care of the last sample (next == $num_in_points)
            if ($next == $num_in_points) {
                $value = $points[$prev];
            } else {
                $value = (1 - $d) * $points[$prev] + $d * $points[$next];
            }

            $output[$i] = $value;
        }
    }
    // Reduce
    else
    {
        // Init
        $spanForNewSamples    = $num_in_points / $num_out_points;
        $j                    = 0;
        $leftOverFromPrevious = 0;

        // Reduce (algorithm implemented by Dema)
        for ($i = 0; $i < $num_out_points; $i++) {
            $upTo        = ($i + 1) * $spanForNewSamples;
            $toJ         = floor($upTo);
            $rest        = $upTo - $toJ;
            $accumulator = $leftOverFromPrevious;

            // accumulating old samples that fall ENTIRELY on the span
            while ($j < $toJ) {
                $accumulator += $points[$j];
                $j++;
            }

            // for old samples in between two spans accumulating on the current new sample only the relevant span (rest * value)
            // and leaving the remaining (1-rest) on the subsequent new sample
            if ($rest > 0) {
                if ($j < $num_in_points - 1) {
                    $j++;
                    $accumulator += $points[$j] * $rest;
                    $leftOverFromPrevious = $points[$j] * (1 - $rest);
                }
            } else {
                $leftOverFromPrevious = 0;
            }

            // All merged points MUST be on the same scale, so / $spanForNewSamples
            $output[$i] = $accumulator / $spanForNewSamples;
        }
    }

    return $output;
}