Add new subsystem for applying envelopes (such as volume ramps) to audio signals

git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@2082 fefdeb5f-60dc-0310-8127-8f9354f1896f
This commit is contained in:
Lennart Poettering 2007-11-24 16:26:49 +00:00
parent 2d34bca4a1
commit 95a98fe6f2
4 changed files with 1094 additions and 1 deletions

View file

@ -253,7 +253,8 @@ noinst_PROGRAMS = \
resampler-test \
smoother-test \
mix-test \
remix-test
remix-test \
envelope-test
if HAVE_SIGXCPU
noinst_PROGRAMS += \
@ -418,6 +419,11 @@ smoother_test_LDADD = $(AM_LDADD) libpulsecore.la
smoother_test_CFLAGS = $(AM_CFLAGS)
smoother_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
envelope_test_SOURCES = tests/envelope-test.c
envelope_test_LDADD = $(AM_LDADD) libpulsecore.la
envelope_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
envelope_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
###################################
# Client library #
###################################
@ -749,6 +755,7 @@ libpulsecore_la_SOURCES += \
pulsecore/once.c pulsecore/once.h \
pulsecore/time-smoother.c pulsecore/time-smoother.h \
pulsecore/start-child.c pulsecore/start-child.h \
pulsecore/envelope.c pulsecore/envelope.h \
$(PA_THREAD_OBJS)
if OS_IS_WIN32

783
src/pulsecore/envelope.c Normal file
View file

@ -0,0 +1,783 @@
/* $Id$ */
/***
This file is part of PulseAudio.
Copyright 2007 Lennart Poettering
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <pulse/sample.h>
#include <pulse/xmalloc.h>
#include <pulsecore/endianmacros.h>
#include <pulsecore/memchunk.h>
#include <pulsecore/macro.h>
#include <pulsecore/flist.h>
#include <pulsecore/semaphore.h>
#include <pulsecore/g711.h>
#include "envelope.h"
/*
Envelope subsystem for applying linear interpolated volume
envelopes on audio data. If multiple enevelopes shall be applied
at the same time, the "minimum" envelope is determined and
applied.
Envelopes are defined in a statically allocated constant structure
pa_envelope_def. It may be activated using pa_envelope_add(). And
already active envelope may be replaced with pa_envelope_replace()
and removed with pa_envelope_remove().The combined "minimum"
envelope can be applied to audio data with pa_envelope_apply().
_apply() on one hand and _add()/_replace()/_remove() on the other
can be executed in seperate threads, in which case no locking is
used.
*/
PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree);
struct pa_envelope_item {
PA_LLIST_FIELDS(pa_envelope_item);
const pa_envelope_def *def;
pa_usec_t start_x;
union {
int32_t i;
float f;
} start_y;
unsigned j;
};
enum envelope_state {
STATE_VALID0,
STATE_VALID1,
STATE_READ0,
STATE_READ1,
STATE_WAIT0,
STATE_WAIT1,
STATE_WRITE0,
STATE_WRITE1
};
struct pa_envelope {
pa_sample_spec sample_spec;
PA_LLIST_HEAD(pa_envelope_item, items);
pa_atomic_t state;
size_t x;
struct {
unsigned n_points, n_allocated, n_current;
size_t *x;
union {
int32_t *i;
float *f;
} y;
size_t cached_dx;
int32_t cached_dy_i;
float cached_dy_dx;
pa_bool_t cached_valid;
} points[2];
pa_bool_t is_float;
pa_semaphore *semaphore;
};
pa_envelope *pa_envelope_new(const pa_sample_spec *ss) {
pa_envelope *e;
pa_assert(ss);
e = pa_xnew(pa_envelope, 1);
e->sample_spec = *ss;
PA_LLIST_HEAD_INIT(pa_envelope_item, e->items);
e->x = 0;
e->points[0].n_points = e->points[1].n_points = 0;
e->points[0].n_allocated = e->points[1].n_allocated = 0;
e->points[0].n_current = e->points[1].n_current = 0;
e->points[0].x = e->points[1].x = NULL;
e->points[0].y.i = e->points[1].y.i = NULL;
e->points[0].cached_valid = e->points[1].cached_valid = FALSE;
pa_atomic_store(&e->state, STATE_VALID0);
e->is_float =
ss->format == PA_SAMPLE_FLOAT32LE ||
ss->format == PA_SAMPLE_FLOAT32BE;
e->semaphore = pa_semaphore_new(0);
return e;
}
void pa_envelope_free(pa_envelope *e) {
pa_assert(e);
while (e->items)
pa_envelope_remove(e, e->items);
pa_xfree(e->points[0].x);
pa_xfree(e->points[1].x);
pa_xfree(e->points[0].y.i);
pa_xfree(e->points[1].y.i);
pa_semaphore_free(e->semaphore);
pa_xfree(e);
}
static int32_t linear_interpolate_int(pa_usec_t x1, int32_t _y1, pa_usec_t x2, int32_t y2, pa_usec_t x3) {
return (int32_t) (_y1 + (x3 - x1) * (float) (y2 - _y1) / (float) (x2 - x1));
}
static float linear_interpolate_float(pa_usec_t x1, float _y1, pa_usec_t x2, float y2, pa_usec_t x3) {
return _y1 + (x3 - x1) * (y2 - _y1) / (x2 - x1);
}
static int32_t item_get_int(pa_envelope_item *i, pa_usec_t x) {
pa_assert(i);
if (x <= i->start_x)
return i->start_y.i;
x -= i->start_x;
if (x <= i->def->points_x[0])
return linear_interpolate_int(0, i->start_y.i,
i->def->points_x[0], i->def->points_y.i[0], x);
if (x >= i->def->points_x[i->def->n_points-1])
return i->def->points_y.i[i->def->n_points-1];
pa_assert(i->j > 0);
pa_assert(i->def->points_x[i->j-1] <= x);
pa_assert(x < i->def->points_x[i->j]);
return linear_interpolate_int(i->def->points_x[i->j-1], i->def->points_y.i[i->j-1],
i->def->points_x[i->j], i->def->points_y.i[i->j], x);
}
static float item_get_float(pa_envelope_item *i, pa_usec_t x) {
pa_assert(i);
if (x <= i->start_x)
return i->start_y.f;
x -= i->start_x;
if (x <= i->def->points_x[0])
return linear_interpolate_float(0, i->start_y.f,
i->def->points_x[0], i->def->points_y.f[0], x);
if (x >= i->def->points_x[i->def->n_points-1])
return i->def->points_y.f[i->def->n_points-1];
pa_assert(i->j > 0);
pa_assert(i->def->points_x[i->j-1] <= x);
pa_assert(x < i->def->points_x[i->j]);
return linear_interpolate_float(i->def->points_x[i->j-1], i->def->points_y.f[i->j-1],
i->def->points_x[i->j], i->def->points_y.f[i->j], x);
}
static void envelope_begin_write(pa_envelope *e, int *v) {
enum envelope_state new_state, old_state;
pa_bool_t wait_sem;
pa_assert(e);
pa_assert(v);
for (;;) {
do {
wait_sem = FALSE;
old_state = pa_atomic_load(&e->state);
switch (old_state) {
case STATE_VALID0:
*v = 1;
new_state = STATE_WRITE0;
break;
case STATE_VALID1:
*v = 0;
new_state = STATE_WRITE1;
break;
case STATE_READ0:
new_state = STATE_WAIT0;
wait_sem = TRUE;
break;
case STATE_READ1:
new_state = STATE_WAIT1;
wait_sem = TRUE;
break;
default:
pa_assert_not_reached();
}
} while (!pa_atomic_cmpxchg(&e->state, old_state, new_state));
if (!wait_sem)
break;
pa_semaphore_wait(e->semaphore);
}
}
static pa_bool_t envelope_commit_write(pa_envelope *e, int v) {
enum envelope_state new_state, old_state;
pa_assert(e);
do {
old_state = pa_atomic_load(&e->state);
switch (old_state) {
case STATE_WRITE0:
pa_assert(v == 1);
new_state = STATE_VALID1;
break;
case STATE_WRITE1:
pa_assert(v == 0);
new_state = STATE_VALID0;
break;
case STATE_VALID0:
case STATE_VALID1:
case STATE_READ0:
case STATE_READ1:
return FALSE;
default:
pa_assert_not_reached();
}
} while (!pa_atomic_cmpxchg(&e->state, old_state, new_state));
return TRUE;
}
static void envelope_begin_read(pa_envelope *e, int *v) {
enum envelope_state new_state, old_state;
pa_assert(e);
pa_assert(v);
do {
old_state = pa_atomic_load(&e->state);
switch (old_state) {
case STATE_VALID0:
case STATE_WRITE0:
*v = 0;
new_state = STATE_READ0;
break;
case STATE_VALID1:
case STATE_WRITE1:
*v = 1;
new_state = STATE_READ1;
break;
default:
pa_assert_not_reached();
}
} while (!pa_atomic_cmpxchg(&e->state, old_state, new_state));
}
static void envelope_commit_read(pa_envelope *e, int v) {
enum envelope_state new_state, old_state;
pa_bool_t post_sem;
pa_assert(e);
do {
post_sem = FALSE;
old_state = pa_atomic_load(&e->state);
switch (old_state) {
case STATE_READ0:
pa_assert(v == 0);
new_state = STATE_VALID0;
break;
case STATE_READ1:
pa_assert(v == 1);
new_state = STATE_VALID1;
break;
case STATE_WAIT0:
pa_assert(v == 0);
new_state = STATE_VALID0;
post_sem = TRUE;
break;
case STATE_WAIT1:
pa_assert(v == 1);
new_state = STATE_VALID1;
post_sem = TRUE;
break;
default:
pa_assert_not_reached();
}
} while (!pa_atomic_cmpxchg(&e->state, old_state, new_state));
if (post_sem)
pa_semaphore_post(e->semaphore);
}
static void envelope_merge(pa_envelope *e, int v) {
e->points[v].n_points = 0;
if (e->items) {
pa_envelope_item *i;
pa_usec_t x = (pa_usec_t) -1;
for (i = e->items; i; i = i->next)
i->j = 0;
for (;;) {
pa_bool_t min_is_set;
pa_envelope_item *s = NULL;
/* Let's find the next spot on the X axis to analyze */
for (i = e->items; i; i = i->next) {
for (;;) {
if (i->j >= i->def->n_points)
break;
if ((x != (pa_usec_t) -1) && i->start_x + i->def->points_x[i->j] <= x) {
i->j++;
continue;
}
if (!s || (i->start_x + i->def->points_x[i->j] < s->start_x + s->def->points_x[s->j]))
s = i;
break;
}
}
if (!s)
break;
if (e->points[v].n_points >= e->points[v].n_allocated) {
e->points[v].n_allocated = MAX(e->points[v].n_points*2, PA_ENVELOPE_POINTS_MAX);
e->points[v].x = pa_xrealloc(e->points[v].x, sizeof(size_t) * e->points[v].n_allocated);
e->points[v].y.i = pa_xrealloc(e->points[v].y.i, sizeof(int32_t) * e->points[v].n_allocated);
}
x = s->start_x + s->def->points_x[s->j];
e->points[v].x[e->points[v].n_points] = pa_usec_to_bytes(x, &e->sample_spec);
min_is_set = FALSE;
/* Now let's find the lowest value */
if (e->is_float) {
float min_f;
for (i = e->items; i; i = i->next) {
float f = item_get_float(i, x);
if (!min_is_set || f < min_f) {
min_f = f;
min_is_set = TRUE;
}
}
e->points[v].y.f[e->points[v].n_points] = min_f;
} else {
int32_t min_k;
for (i = e->items; i; i = i->next) {
int32_t k = item_get_int(i, x);
if (!min_is_set || k < min_k) {
min_k = k;
min_is_set = TRUE;
}
}
e->points[v].y.i[e->points[v].n_points] = min_k;
}
pa_assert_se(min_is_set);
e->points[v].n_points++;
}
}
e->points[v].n_current = 0;
e->points[v].cached_valid = FALSE;
}
pa_envelope_item *pa_envelope_add(pa_envelope *e, const pa_envelope_def *def) {
pa_envelope_item *i;
int v;
pa_assert(e);
pa_assert(def);
pa_assert(def->n_points > 0);
if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items))))
i = pa_xnew(pa_envelope_item, 1);
i->def = def;
if (e->is_float)
i->start_y.f = def->points_y.f[0];
else
i->start_y.i = def->points_y.i[0];
PA_LLIST_PREPEND(pa_envelope_item, e->items, i);
envelope_begin_write(e, &v);
do {
i->start_x = pa_bytes_to_usec(e->x, &e->sample_spec);
envelope_merge(e, v);
} while (!envelope_commit_write(e, v));
return i;
}
pa_envelope_item *pa_envelope_replace(pa_envelope *e, pa_envelope_item *i, const pa_envelope_def *def) {
pa_usec_t x;
int v;
pa_assert(e);
pa_assert(i);
pa_assert(def->n_points > 0);
envelope_begin_write(e, &v);
for (;;) {
float saved_f;
int32_t saved_i;
uint64_t saved_start_x;
const pa_envelope_def *saved_def;
x = pa_bytes_to_usec(e->x, &e->sample_spec);
if (e->is_float) {
saved_f = i->start_y.f;
i->start_y.f = item_get_float(i, x);
} else {
saved_i = i->start_y.i;
i->start_y.i = item_get_int(i, x);
}
saved_start_x = i->start_x;
saved_def = i->def;
i->start_x = x;
i->def = def;
envelope_merge(e, v);
if (envelope_commit_write(e, v))
break;
i->start_x = saved_start_x;
i->def = saved_def;
if (e->is_float)
i->start_y.f = saved_f;
else
i->start_y.i = saved_i;
}
return i;
}
void pa_envelope_remove(pa_envelope *e, pa_envelope_item *i) {
int v;
pa_assert(e);
pa_assert(i);
PA_LLIST_REMOVE(pa_envelope_item, e->items, i);
if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0)
pa_xfree(i);
envelope_begin_write(e, &v);
do {
envelope_merge(e, v);
} while (!envelope_commit_write(e, v));
}
static int32_t linear_get_int(pa_envelope *e, int v) {
pa_assert(e);
/* The repeated division could be replaced by Bresenham, as an
* optimization */
if (e->x < e->points[v].x[0])
return e->points[v].y.i[0];
for (;;) {
if (e->points[v].n_current+1 >= e->points[v].n_points)
return e->points[v].y.i[e->points[v].n_points-1];
if (e->x < e->points[v].x[e->points[v].n_current+1])
break;
e->points[v].n_current++;
e->points[v].cached_valid = FALSE;
}
if (!e->points[v].cached_valid) {
e->points[v].cached_dx = e->points[v].x[e->points[v].n_current+1] - e->points[v].x[e->points[v].n_current];
e->points[v].cached_dy_i = e->points[v].y.i[e->points[v].n_current+1] - e->points[v].y.i[e->points[v].n_current];
e->points[v].cached_valid = TRUE;
}
return e->points[v].y.i[e->points[v].n_current] + (e->points[v].cached_dy_i * (int32_t) (e->x - e->points[v].x[e->points[v].n_current])) / (int32_t) e->points[v].cached_dx;
}
static float linear_get_float(pa_envelope *e, int v) {
pa_assert(e);
if (e->x < e->points[v].x[0])
return e->points[v].y.f[0];
for (;;) {
if (e->points[v].n_current+1 >= e->points[v].n_points)
return e->points[v].y.f[e->points[v].n_points-1];
if (e->x < e->points[v].x[e->points[v].n_current+1])
break;
e->points[v].n_current++;
e->points[v].cached_valid = FALSE;
}
if (!e->points[v].cached_valid) {
e->points[v].cached_dy_dx =
(e->points[v].y.f[e->points[v].n_current+1] - e->points[v].y.f[e->points[v].n_current]) /
(e->points[v].x[e->points[v].n_current+1] - e->points[v].x[e->points[v].n_current]);
e->points[v].cached_valid = TRUE;
}
return e->points[v].y.f[e->points[v].n_current] + (e->x - e->points[v].x[e->points[v].n_current]) * e->points[v].cached_dy_dx;
}
void pa_envelope_apply(pa_envelope *e, pa_memchunk *chunk) {
int v;
pa_assert(e);
pa_assert(chunk);
envelope_begin_read(e, &v);
if (e->points[v].n_points > 0) {
void *p;
size_t fs, n;
pa_memchunk_make_writable(chunk, 0);
p = (uint8_t*) pa_memblock_acquire(chunk->memblock) + chunk->index;
fs = pa_frame_size(&e->sample_spec);
n = chunk->length;
switch (e->sample_spec.format) {
case PA_SAMPLE_U8: {
uint8_t *t;
for (t = p; n > 0; n -= fs) {
int16_t factor = linear_get_int(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++)
*t = (uint8_t) (((factor * ((int16_t) *t - 0x80)) / 0x10000) + 0x80);
}
break;
}
case PA_SAMPLE_ULAW: {
uint8_t *t;
for (t = p; n > 0; n -= fs) {
int16_t factor = linear_get_int(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++) {
int16_t k = st_ulaw2linear16(*t);
*t = (uint8_t) st_14linear2ulaw(((factor * k) / 0x10000) >> 2);
}
}
break;
}
case PA_SAMPLE_ALAW: {
uint8_t *t;
for (t = p; n > 0; n -= fs) {
int16_t factor = linear_get_int(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++) {
int16_t k = st_alaw2linear16(*t);
*t = (uint8_t) st_13linear2alaw(((factor * k) / 0x10000) >> 3);
}
}
break;
}
case PA_SAMPLE_S16NE: {
int16_t *t;
for (t = p; n > 0; n -= fs) {
int32_t factor = linear_get_int(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++)
*t = (factor * *t) / 0x10000;
}
break;
}
case PA_SAMPLE_S16RE: {
int16_t *t;
for (t = p; n > 0; n -= fs) {
int32_t factor = linear_get_int(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++) {
int16_t r = (factor * PA_INT16_SWAP(*t)) / 0x10000;
*t = PA_INT16_SWAP(r);
}
}
break;
}
case PA_SAMPLE_S32NE: {
int32_t *t;
for (t = p; n > 0; n -= fs) {
int32_t factor = linear_get_int(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++)
*t = (int32_t) (((int64_t) factor * (int64_t) *t) / 0x10000);
}
break;
}
case PA_SAMPLE_S32RE: {
int32_t *t;
for (t = p; n > 0; n -= fs) {
int32_t factor = linear_get_int(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++) {
int32_t r = (int32_t) (((int64_t) factor * (int64_t) PA_INT32_SWAP(*t)) / 0x10000);
*t = PA_INT32_SWAP(r);
}
}
break;
}
case PA_SAMPLE_FLOAT32NE: {
float *t;
for (t = p; n > 0; n -= fs) {
float factor = linear_get_float(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++)
*t = *t * factor;
}
break;
}
case PA_SAMPLE_FLOAT32RE: {
float *t;
for (t = p; n > 0; n -= fs) {
float factor = linear_get_float(e, v);
unsigned c;
e->x += fs;
for (c = 0; c < e->sample_spec.channels; c++, t++) {
float r = PA_FLOAT32_SWAP(*t) * factor;
*t = PA_FLOAT32_SWAP(r);
}
}
break;
}
case PA_SAMPLE_MAX:
case PA_SAMPLE_INVALID:
pa_assert_not_reached();
}
pa_memblock_release(chunk->memblock);
e->x += chunk->length;
} else {
/* When we have no envelope to apply we reset our origin */
e->x = 0;
}
envelope_commit_read(e, v);
}
void pa_envelope_rewind(pa_envelope *e, size_t n_bytes) {
int v;
pa_assert(e);
envelope_begin_read(e, &v);
if (n_bytes < e->x)
e->x -= n_bytes;
else
e->x = 0;
e->points[v].n_current = 0;
e->points[v].cached_valid = FALSE;
envelope_commit_read(e, v);
}

55
src/pulsecore/envelope.h Normal file
View file

@ -0,0 +1,55 @@
#ifndef foopulseenvelopehfoo
#define foopulseenvelopehfoo
/* $Id$ */
/***
This file is part of PulseAudio.
Copyright 2007 Lennart Poettering
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#include <pulsecore/macro.h>
#include <pulsecore/memchunk.h>
#include <pulse/sample.h>
#define PA_ENVELOPE_POINTS_MAX 4
typedef struct pa_envelope pa_envelope;
typedef struct pa_envelope_item pa_envelope_item;
typedef struct pa_envelope_def {
unsigned n_points;
pa_usec_t points_x[PA_ENVELOPE_POINTS_MAX];
struct {
int32_t i[PA_ENVELOPE_POINTS_MAX];
float f[PA_ENVELOPE_POINTS_MAX];
} points_y;
} pa_envelope_def;
pa_envelope *pa_envelope_new(const pa_sample_spec *ss);
void pa_envelope_free(pa_envelope *e);
pa_envelope_item *pa_envelope_add(pa_envelope *e, const pa_envelope_def *def);
pa_envelope_item *pa_envelope_replace(pa_envelope *e, pa_envelope_item *i, const pa_envelope_def *def);
void pa_envelope_remove(pa_envelope *e, pa_envelope_item *i);
void pa_envelope_apply(pa_envelope *e, pa_memchunk *chunk);
void pa_envelope_rewind(pa_envelope *e, size_t n_bytes);
#endif

248
src/tests/envelope-test.c Normal file
View file

@ -0,0 +1,248 @@
/* $Id$ */
/***
This file is part of PulseAudio.
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <pulse/sample.h>
#include <pulse/volume.h>
#include <pulse/timeval.h>
#include <pulsecore/envelope.h>
#include <pulsecore/macro.h>
#include <pulsecore/endianmacros.h>
#include <pulsecore/memblock.h>
#include <pulsecore/sample-util.h>
#include <liboil/liboil.h>
const pa_envelope_def ramp_down = {
.n_points = 2,
.points_x = { 100*PA_USEC_PER_MSEC, 300*PA_USEC_PER_MSEC },
.points_y = {
.f = { 1.0, 0.2 },
.i = { 0x10000, 0x10000/5 }
}
};
const pa_envelope_def ramp_up = {
.n_points = 2,
.points_x = { 100*PA_USEC_PER_MSEC, 300*PA_USEC_PER_MSEC },
.points_y = {
.f = { 0.2, 1.0 },
.i = { 0x10000/5, 0x10000 }
}
};
const pa_envelope_def ramp_down2 = {
.n_points = 2,
.points_x = { 50*PA_USEC_PER_MSEC, 900*PA_USEC_PER_MSEC },
.points_y = {
.f = { 0.8, 0.7 },
.i = { 0x10000*4/5, 0x10000*7/10 }
}
};
const pa_envelope_def ramp_up2 = {
.n_points = 2,
.points_x = { 50*PA_USEC_PER_MSEC, 900*PA_USEC_PER_MSEC },
.points_y = {
.f = { 0.7, 0.9 },
.i = { 0x10000*7/10, 0x10000*9/10 }
}
};
static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) {
void *d;
unsigned i;
static unsigned j = 0;
d = pa_memblock_acquire(chunk->memblock);
switch (ss->format) {
case PA_SAMPLE_U8:
case PA_SAMPLE_ULAW:
case PA_SAMPLE_ALAW: {
uint8_t *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
printf("0x%02x ", *(u++));
break;
}
case PA_SAMPLE_S16NE:
case PA_SAMPLE_S16RE: {
int16_t *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
printf("%i\t%i\n", j++, *(u++));
break;
}
case PA_SAMPLE_S32NE:
case PA_SAMPLE_S32RE: {
int32_t *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
printf("%i\t%i\n", j++, *(u++));
break;
}
case PA_SAMPLE_FLOAT32NE:
case PA_SAMPLE_FLOAT32RE: {
float *u = d;
for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
printf("%i\t%1.3g\n", j++, PA_MAYBE_FLOAT32_SWAP(ss->format == PA_SAMPLE_FLOAT32RE, *u));
u++;
}
break;
}
default:
pa_assert_not_reached();
}
printf("\n");
pa_memblock_release(chunk->memblock);
}
static pa_memblock * generate_block(pa_mempool *pool, const pa_sample_spec *ss) {
pa_memblock *block;
void *d;
unsigned n_samples;
block = pa_memblock_new(pool, pa_bytes_per_second(ss));
n_samples = pa_memblock_get_length(block) / pa_sample_size(ss);
d = pa_memblock_acquire(block);
switch (ss->format) {
case PA_SAMPLE_S16NE:
case PA_SAMPLE_S16RE: {
int16_t *i;
for (i = d; n_samples > 0; n_samples--, i++)
*i = 0x7FFF;
break;
}
case PA_SAMPLE_S32NE:
case PA_SAMPLE_S32RE: {
int32_t *i;
for (i = d; n_samples > 0; n_samples--, i++)
*i = 0x7FFFFFFF;
break;
}
case PA_SAMPLE_FLOAT32RE:
case PA_SAMPLE_FLOAT32NE: {
float *f;
for (f = d; n_samples > 0; n_samples--, f++)
*f = PA_MAYBE_FLOAT32_SWAP(ss->format == PA_SAMPLE_FLOAT32RE, 1.0);
break;
}
default:
pa_assert_not_reached();
}
pa_memblock_release(block);
return block;
}
int main(int argc, char *argv[]) {
pa_mempool *pool;
pa_memblock *block;
pa_memchunk chunk;
pa_envelope *envelope;
pa_envelope_item *item1, *item2;
const pa_sample_spec ss = {
.format = PA_SAMPLE_S16NE,
.channels = 1,
.rate = 200
};
const pa_cvolume v = {
.channels = 1,
.values = { PA_VOLUME_NORM, PA_VOLUME_NORM/2 }
};
oil_init();
pa_log_set_maximal_level(PA_LOG_DEBUG);
pa_assert_se(pool = pa_mempool_new(FALSE));
pa_assert_se(envelope = pa_envelope_new(&ss));
block = generate_block(pool, &ss);
chunk.memblock = pa_memblock_ref(block);
chunk.length = pa_memblock_get_length(block);
chunk.index = 0;
pa_volume_memchunk(&chunk, &ss, &v);
item1 = pa_envelope_add(envelope, &ramp_down);
item2 = pa_envelope_add(envelope, &ramp_down2);
pa_envelope_apply(envelope, &chunk);
dump_block(&ss, &chunk);
pa_memblock_unref(chunk.memblock);
chunk.memblock = pa_memblock_ref(block);
chunk.length = pa_memblock_get_length(block);
chunk.index = 0;
item1 = pa_envelope_replace(envelope, item1, &ramp_up);
item2 = pa_envelope_replace(envelope, item2, &ramp_up2);
pa_envelope_apply(envelope, &chunk);
dump_block(&ss, &chunk);
pa_memblock_unref(chunk.memblock);
pa_envelope_remove(envelope, item1);
pa_envelope_remove(envelope, item2);
pa_envelope_free(envelope);
pa_memblock_unref(block);
pa_mempool_free(pool);
return 0;
}