You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
8.6 KiB
271 lines
8.6 KiB
![]()
6 years ago
|
/*
|
||
|
LV2 audio peaks utilities
|
||
|
Copyright 2016 David Robillard <d@drobilla.net>
|
||
|
|
||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||
|
purpose with or without fee is hereby granted, provided that the above
|
||
|
copyright notice and this permission notice appear in all copies.
|
||
|
|
||
|
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
This file defines utilities for sending and receiving audio peaks for
|
||
|
waveform display. The functionality is divided into two objects:
|
||
|
PeaksSender, for sending peaks updates from the plugin, and PeaksReceiver,
|
||
|
for receiving such updates and caching the peaks.
|
||
|
|
||
|
This allows peaks for a waveform of any size at any resolution to be
|
||
|
requested, with reasonably sized incremental updates sent over plugin ports.
|
||
|
*/
|
||
|
|
||
|
#ifndef PEAKS_H_INCLUDED
|
||
|
#define PEAKS_H_INCLUDED
|
||
|
|
||
|
#include <math.h>
|
||
|
|
||
|
#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
|
||
|
|
||
|
#define PEAKS_URI "http://lv2plug.in/ns/peaks#"
|
||
|
#define PEAKS__PeakUpdate PEAKS_URI "PeakUpdate"
|
||
|
#define PEAKS__magnitudes PEAKS_URI "magnitudes"
|
||
|
#define PEAKS__offset PEAKS_URI "offset"
|
||
|
#define PEAKS__total PEAKS_URI "total"
|
||
|
|
||
|
#ifndef MIN
|
||
|
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||
|
#endif
|
||
|
#ifndef MAX
|
||
|
# define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||
|
#endif
|
||
|
|
||
|
typedef struct {
|
||
|
LV2_URID atom_Float;
|
||
|
LV2_URID atom_Int;
|
||
|
LV2_URID atom_Vector;
|
||
|
LV2_URID peaks_PeakUpdate;
|
||
|
LV2_URID peaks_magnitudes;
|
||
|
LV2_URID peaks_offset;
|
||
|
LV2_URID peaks_total;
|
||
|
} PeaksURIs;
|
||
|
|
||
|
typedef struct {
|
||
|
PeaksURIs uris; ///< URIDs used in protocol
|
||
|
const float* samples; ///< Sample data
|
||
|
uint32_t n_samples; ///< Total number of samples
|
||
|
uint32_t n_peaks; ///< Total number of peaks
|
||
|
uint32_t current_offset; ///< Current peak offset
|
||
|
bool sending; ///< True iff currently sending
|
||
|
} PeaksSender;
|
||
|
|
||
|
typedef struct {
|
||
|
PeaksURIs uris; ///< URIDs used in protocol
|
||
|
float* peaks; ///< Received peaks, or zeroes
|
||
|
uint32_t n_peaks; ///< Total number of peaks
|
||
|
} PeaksReceiver;
|
||
|
|
||
|
/**
|
||
|
Map URIs used in the peaks protocol.
|
||
|
*/
|
||
|
static inline void
|
||
|
peaks_map_uris(PeaksURIs* uris, LV2_URID_Map* map)
|
||
|
{
|
||
|
uris->atom_Float = map->map(map->handle, LV2_ATOM__Float);
|
||
|
uris->atom_Int = map->map(map->handle, LV2_ATOM__Int);
|
||
|
uris->atom_Vector = map->map(map->handle, LV2_ATOM__Vector);
|
||
|
uris->peaks_PeakUpdate = map->map(map->handle, PEAKS__PeakUpdate);
|
||
|
uris->peaks_magnitudes = map->map(map->handle, PEAKS__magnitudes);
|
||
|
uris->peaks_offset = map->map(map->handle, PEAKS__offset);
|
||
|
uris->peaks_total = map->map(map->handle, PEAKS__total);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Initialise peaks sender. The new sender is inactive and will do nothing
|
||
|
when `peaks_sender_send()` is called, until a transmission is started with
|
||
|
`peaks_sender_start()`.
|
||
|
*/
|
||
|
static inline PeaksSender*
|
||
|
peaks_sender_init(PeaksSender* sender, LV2_URID_Map* map)
|
||
|
{
|
||
|
memset(sender, 0, sizeof(*sender));
|
||
|
peaks_map_uris(&sender->uris, map);
|
||
|
return sender;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Prepare to start a new peaks transmission. After this is called, the peaks
|
||
|
can be sent with successive calls to `peaks_sender_send()`.
|
||
|
*/
|
||
|
static inline void
|
||
|
peaks_sender_start(PeaksSender* sender,
|
||
|
const float* samples,
|
||
|
uint32_t n_samples,
|
||
|
uint32_t n_peaks)
|
||
|
{
|
||
|
sender->samples = samples;
|
||
|
sender->n_samples = n_samples;
|
||
|
sender->n_peaks = n_peaks;
|
||
|
sender->current_offset = 0;
|
||
|
sender->sending = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Forge a message which sends a range of peaks. Writes a peaks:PeakUpdate
|
||
|
object to `forge`, like:
|
||
|
|
||
|
[source,n3]
|
||
|
----
|
||
|
[]
|
||
|
a peaks:PeakUpdate ;
|
||
|
peaks:offset 256 ;
|
||
|
peaks:total 1024 ;
|
||
|
peaks:magnitudes [ 0.2f, 0.3f, ... ] .
|
||
|
----
|
||
|
*/
|
||
|
static inline bool
|
||
|
peaks_sender_send(PeaksSender* sender,
|
||
|
LV2_Atom_Forge* forge,
|
||
|
uint32_t n_frames,
|
||
|
uint32_t offset)
|
||
|
{
|
||
|
const PeaksURIs* uris = &sender->uris;
|
||
|
if (!sender->sending || sender->current_offset >= sender->n_peaks) {
|
||
|
return sender->sending = false;
|
||
|
}
|
||
|
|
||
|
// Start PeakUpdate object
|
||
|
lv2_atom_forge_frame_time(forge, offset);
|
||
|
LV2_Atom_Forge_Frame frame;
|
||
|
lv2_atom_forge_object(forge, &frame, 0, uris->peaks_PeakUpdate);
|
||
|
|
||
|
// eg:offset = OFFSET
|
||
|
lv2_atom_forge_key(forge, uris->peaks_offset);
|
||
|
lv2_atom_forge_int(forge, sender->current_offset);
|
||
|
|
||
|
// eg:total = TOTAL
|
||
|
lv2_atom_forge_key(forge, uris->peaks_total);
|
||
|
lv2_atom_forge_int(forge, sender->n_peaks);
|
||
|
|
||
|
// eg:magnitudes = Vector<Float>(PEAK, PEAK, ...)
|
||
|
lv2_atom_forge_key(forge, uris->peaks_magnitudes);
|
||
|
LV2_Atom_Forge_Frame vec_frame;
|
||
|
lv2_atom_forge_vector_head(
|
||
|
forge, &vec_frame, sizeof(float), uris->atom_Float);
|
||
|
|
||
|
// Calculate how many peaks to send this update
|
||
|
const int chunk_size = MAX(1, sender->n_samples / sender->n_peaks);
|
||
|
const uint32_t space = forge->size - forge->offset;
|
||
|
const uint32_t remaining = sender->n_peaks - sender->current_offset;
|
||
|
const int n_update = MIN(remaining,
|
||
|
MIN(n_frames / 4, space / sizeof(float)));
|
||
|
|
||
|
// Calculate peak (maximum magnitude) for each chunk
|
||
|
for (int i = 0; i < n_update; ++i) {
|
||
|
const int start = (sender->current_offset + i) * chunk_size;
|
||
|
float peak = 0.0f;
|
||
|
for (int j = 0; j < chunk_size; ++j) {
|
||
|
peak = fmaxf(peak, fabsf(sender->samples[start + j]));
|
||
|
}
|
||
|
lv2_atom_forge_float(forge, peak);
|
||
|
}
|
||
|
|
||
|
// Finish message
|
||
|
lv2_atom_forge_pop(forge, &vec_frame);
|
||
|
lv2_atom_forge_pop(forge, &frame);
|
||
|
|
||
|
sender->current_offset += n_update;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Initialise a peaks receiver. The receiver stores an array of all peaks,
|
||
|
which is updated incrementally with peaks_receiver_receive().
|
||
|
*/
|
||
|
static inline PeaksReceiver*
|
||
|
peaks_receiver_init(PeaksReceiver* receiver, LV2_URID_Map* map)
|
||
|
{
|
||
|
memset(receiver, 0, sizeof(*receiver));
|
||
|
peaks_map_uris(&receiver->uris, map);
|
||
|
return receiver;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Clear stored peaks and free all memory. This should be called when the
|
||
|
peaks are to be updated with a different audio source.
|
||
|
*/
|
||
|
static inline void
|
||
|
peaks_receiver_clear(PeaksReceiver* receiver)
|
||
|
{
|
||
|
free(receiver->peaks);
|
||
|
receiver->peaks = NULL;
|
||
|
receiver->n_peaks = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Handle PeakUpdate message.
|
||
|
|
||
|
The stored peaks array is updated with the slice of peaks in `update`,
|
||
|
resizing if necessary while preserving contents.
|
||
|
|
||
|
Returns 0 if peaks have been updated, negative on error.
|
||
|
*/
|
||
|
static inline int
|
||
|
peaks_receiver_receive(PeaksReceiver* receiver, const LV2_Atom_Object* update)
|
||
|
{
|
||
|
const PeaksURIs* uris = &receiver->uris;
|
||
|
|
||
|
// Get properties of interest from update
|
||
|
const LV2_Atom_Int* offset = NULL;
|
||
|
const LV2_Atom_Int* total = NULL;
|
||
|
const LV2_Atom_Vector* peaks = NULL;
|
||
|
lv2_atom_object_get_typed(update,
|
||
|
uris->peaks_offset, &offset, uris->atom_Int,
|
||
|
uris->peaks_total, &total, uris->atom_Int,
|
||
|
uris->peaks_magnitudes, &peaks, uris->atom_Vector,
|
||
|
0);
|
||
|
|
||
|
if (!offset || !total || !peaks ||
|
||
|
peaks->body.child_type != uris->atom_Float) {
|
||
|
return -1; // Invalid update
|
||
|
}
|
||
|
|
||
|
const uint32_t n = (uint32_t)total->body;
|
||
|
if (receiver->n_peaks != n) {
|
||
|
// Update is for a different total number of peaks, resize
|
||
|
receiver->peaks = (float*)realloc(receiver->peaks, n * sizeof(float));
|
||
|
if (receiver->n_peaks > 0 && receiver->n_peaks < n) {
|
||
|
/* The peaks array is being expanded. Copy the old peaks,
|
||
|
duplicating each as necessary to fill the new peaks buffer.
|
||
|
This preserves the current peaks so that the peaks array can be
|
||
|
reasonably drawn at any time, but the resolution will increase
|
||
|
as new updates arrive. */
|
||
|
const int n_per = n / receiver->n_peaks;
|
||
|
for (int i = n - 1; i >= 0; --i) {
|
||
|
receiver->peaks[i] = receiver->peaks[i / n_per];
|
||
|
}
|
||
|
} else if (receiver->n_peaks > 0) {
|
||
|
/* The peak array is being shrunk. Similar to the above. */
|
||
|
const int n_per = receiver->n_peaks / n;
|
||
|
for (int i = n - 1; i >= 0; --i) {
|
||
|
receiver->peaks[i] = receiver->peaks[i * n_per];
|
||
|
}
|
||
|
}
|
||
|
receiver->n_peaks = n;
|
||
|
}
|
||
|
|
||
|
// Copy vector contents to corresponding range in peaks array
|
||
|
memcpy(receiver->peaks + offset->body,
|
||
|
peaks + 1,
|
||
|
peaks->atom.size - sizeof(LV2_Atom_Vector_Body));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#endif // PEAKS_H_INCLUDED
|