541960a110
It seems removing this causes a crash on the Clip+ when playing any file. Appears to be a timing-related issue as replacing the loop with an mdelay() also fixes it. Needs further investigation to identify the real cause of the problem, see FS#13386. Change-Id: Ia93a2887a79b376de50563d6bb3bbc79cee11a1c
550 lines
16 KiB
C
550 lines
16 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2005 Miika Pekkarinen
|
|
* Copyright (C) 2012 Michael Sevakis
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "rbcodecconfig.h"
|
|
#include "platform.h"
|
|
#include "dsp_core.h"
|
|
#include "dsp_sample_io.h"
|
|
|
|
#include "tdspeed.h"
|
|
#include "resample.h"
|
|
|
|
/* Define LOGF_ENABLE to enable logf output in this file */
|
|
/*#define LOGF_ENABLE*/
|
|
#include "logf.h"
|
|
|
|
/* Actually generate the database of stages */
|
|
#define DSP_PROC_DB_CREATE
|
|
#include "dsp_proc_entry.h"
|
|
|
|
#ifndef DSP_PROCESS_START
|
|
/* These do nothing if not previously defined */
|
|
#define DSP_PROCESS_START()
|
|
#define DSP_PROCESS_LOOP()
|
|
#define DSP_PROCESS_END()
|
|
#endif /* !DSP_PROCESS_START */
|
|
|
|
/* Linked lists give fewer loads in processing loop compared to some index
|
|
* list, which is more important than keeping occasionally executed code
|
|
* simple */
|
|
|
|
struct dsp_config
|
|
{
|
|
/** General DSP-local data **/
|
|
struct sample_io_data io_data; /* Sample input-output data (first) */
|
|
uint32_t slot_free_mask; /* Mask of free slots for this DSP */
|
|
uint32_t proc_mask_enabled; /* Mask of enabled stages */
|
|
uint32_t proc_mask_active; /* Mask of active stages */
|
|
struct dsp_proc_slot
|
|
{
|
|
struct dsp_proc_entry proc_entry; /* This enabled stage */
|
|
struct dsp_proc_slot *next; /* Next enabled slot */
|
|
uint32_t mask; /* In place operation mask/flag */
|
|
uint8_t version; /* Sample format version */
|
|
uint8_t db_index; /* Index in database array */
|
|
} *proc_slots; /* Pointer to first in list of enabled
|
|
stages */
|
|
};
|
|
|
|
#define NACT_BIT BIT_N(___DSP_PROC_ID_RESERVED)
|
|
|
|
/* Pool of slots for stages - supports 32 or fewer combined as-is atm. */
|
|
static struct dsp_proc_slot
|
|
dsp_proc_slot_arr[DSP_NUM_PROC_STAGES+DSP_VOICE_NUM_PROC_STAGES] IBSS_ATTR;
|
|
|
|
/* General DSP config */
|
|
static struct dsp_config dsp_conf[DSP_COUNT] IBSS_ATTR;
|
|
|
|
static const dsp_proc_init_fn_type dsp_init_fn[] INITDATA_ATTR = {
|
|
&dsp_timestretch_init,
|
|
&dsp_resample_init,
|
|
};
|
|
|
|
/** Processing stages support functions **/
|
|
static const struct dsp_proc_db_entry *
|
|
proc_db_entry(const struct dsp_proc_slot *s)
|
|
{
|
|
return dsp_proc_database[s->db_index];
|
|
}
|
|
|
|
/* Find the slot for a given enabled id */
|
|
static struct dsp_proc_slot * find_proc_slot(struct dsp_config *dsp,
|
|
unsigned int id)
|
|
{
|
|
const uint32_t mask = BIT_N(id);
|
|
|
|
if (!(dsp->proc_mask_enabled & mask))
|
|
return NULL; /* Not enabled */
|
|
|
|
struct dsp_proc_slot *s = dsp->proc_slots;
|
|
|
|
while (1) /* In proc_mask_enabled == it must be there */
|
|
{
|
|
if (BIT_N(proc_db_entry(s)->id) == mask)
|
|
return s;
|
|
|
|
s = s->next;
|
|
}
|
|
}
|
|
|
|
/* Broadcast to all enabled stages or to the one with the specifically
|
|
* crafted setting */
|
|
static intptr_t proc_broadcast(struct dsp_config *dsp, unsigned int setting,
|
|
intptr_t value)
|
|
{
|
|
bool multi = setting < DSP_PROC_SETTING;
|
|
struct dsp_proc_slot *s;
|
|
|
|
if (multi)
|
|
{
|
|
/* Message to all enabled stages */
|
|
if (dsp_sample_io_configure(&dsp->io_data, setting, &value))
|
|
return value; /* To I/O only */
|
|
|
|
s = dsp->proc_slots;
|
|
}
|
|
else
|
|
{
|
|
/* Message to a particular stage */
|
|
s = find_proc_slot(dsp, setting - DSP_PROC_SETTING);
|
|
}
|
|
|
|
while (s != NULL)
|
|
{
|
|
intptr_t ret = proc_db_entry(s)->configure(
|
|
&s->proc_entry, dsp, setting, value);
|
|
|
|
if (!multi)
|
|
return ret;
|
|
|
|
s = s->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Add an item to the enabled list */
|
|
static struct dsp_proc_slot *
|
|
dsp_proc_enable_enlink(struct dsp_config *dsp, uint32_t mask)
|
|
{
|
|
/* Use the lowest-indexed available slot */
|
|
int slot = find_first_set_bit(dsp->slot_free_mask);
|
|
|
|
if (slot == 32)
|
|
{
|
|
/* Should NOT happen, ever, unless called before init */
|
|
DEBUGF("DSP %d: no slots!\n", (int)dsp_get_id(dsp));
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int db_index = 0, db_index_prev = DSP_NUM_PROC_STAGES;
|
|
|
|
/* Order of enabled list is same as DB array */
|
|
while (1)
|
|
{
|
|
uint32_t m = BIT_N(dsp_proc_database[db_index]->id);
|
|
|
|
if (m == mask)
|
|
break; /* This is the one */
|
|
|
|
if (dsp->proc_mask_enabled & m)
|
|
db_index_prev = db_index;
|
|
|
|
if (++db_index >= DSP_NUM_PROC_STAGES)
|
|
return NULL;
|
|
}
|
|
|
|
struct dsp_proc_slot *s = &dsp_proc_slot_arr[slot];
|
|
|
|
if (db_index_prev < DSP_NUM_PROC_STAGES)
|
|
{
|
|
struct dsp_proc_slot *prev =
|
|
find_proc_slot(dsp, dsp_proc_database[db_index_prev]->id);
|
|
s->next = prev->next;
|
|
prev->next = s;
|
|
}
|
|
else
|
|
{
|
|
s->next = dsp->proc_slots;
|
|
dsp->proc_slots = s;
|
|
}
|
|
|
|
s->mask = mask | NACT_BIT;
|
|
s->version = 0;
|
|
s->db_index = db_index;
|
|
dsp->proc_mask_enabled |= mask;
|
|
dsp->slot_free_mask &= ~BIT_N(slot);
|
|
|
|
return s;
|
|
}
|
|
|
|
/* Remove an item from the enabled list */
|
|
static struct dsp_proc_slot *
|
|
dsp_proc_enable_delink(struct dsp_config *dsp, uint32_t mask)
|
|
{
|
|
struct dsp_proc_slot *s = dsp->proc_slots;
|
|
struct dsp_proc_slot *prev = NULL;
|
|
|
|
while (1) /* In proc_mask_enabled == it must be there */
|
|
{
|
|
if (BIT_N(proc_db_entry(s)->id) == mask)
|
|
{
|
|
if (prev)
|
|
prev->next = s->next;
|
|
else
|
|
dsp->proc_slots = s->next;
|
|
|
|
dsp->proc_mask_enabled &= ~mask;
|
|
dsp->slot_free_mask |= BIT_N(s - dsp_proc_slot_arr);
|
|
return s;
|
|
}
|
|
|
|
prev = s;
|
|
s = s->next;
|
|
}
|
|
}
|
|
|
|
static void dsp_empty_process(struct dsp_proc_entry *this, struct dsp_buffer **buf_p)
|
|
{
|
|
(void)this;
|
|
(void)buf_p;
|
|
logf("%s", __func__);
|
|
}
|
|
|
|
void dsp_proc_enable(struct dsp_config *dsp, enum dsp_proc_ids id,
|
|
bool enable)
|
|
{
|
|
const uint32_t mask = BIT_N(id);
|
|
bool enabled = dsp->proc_mask_enabled & mask;
|
|
|
|
if (enable)
|
|
{
|
|
/* If enabled, just find it in list, if not, link a new one */
|
|
struct dsp_proc_slot *s = enabled ? find_proc_slot(dsp, id) :
|
|
dsp_proc_enable_enlink(dsp, mask);
|
|
|
|
if (s == NULL)
|
|
{
|
|
DEBUGF("DSP- proc id not valid: %d\n", (int)id);
|
|
return;
|
|
}
|
|
|
|
if (!enabled)
|
|
{
|
|
/* New entry - set defaults */
|
|
s->proc_entry.data = 0;
|
|
s->proc_entry.process = dsp_empty_process;
|
|
}
|
|
|
|
enabled = proc_db_entry(s)->configure(&s->proc_entry, dsp,
|
|
DSP_PROC_INIT, enabled) >= 0;
|
|
if (enabled)
|
|
return;
|
|
|
|
DEBUGF("DSP- proc init failed: %d\n", (int)id);
|
|
/* Cleanup below */
|
|
}
|
|
else if (!enabled)
|
|
{
|
|
return; /* No change */
|
|
}
|
|
|
|
dsp_proc_activate(dsp, id, false); /* Deactivate it first */
|
|
struct dsp_proc_slot *s = dsp_proc_enable_delink(dsp, mask);
|
|
proc_db_entry(s)->configure(&s->proc_entry, dsp, DSP_PROC_CLOSE, 0);
|
|
}
|
|
|
|
/* Is the stage specified by the id currently enabled? */
|
|
bool dsp_proc_enabled(struct dsp_config *dsp, enum dsp_proc_ids id)
|
|
{
|
|
return (dsp->proc_mask_enabled & BIT_N(id)) != 0;
|
|
}
|
|
|
|
/* Activate or deactivate a stage */
|
|
void dsp_proc_activate(struct dsp_config *dsp, enum dsp_proc_ids id,
|
|
bool activate)
|
|
{
|
|
const uint32_t mask = BIT_N(id);
|
|
|
|
if (!(dsp->proc_mask_enabled & mask))
|
|
return; /* Not enabled */
|
|
|
|
if (activate != !(dsp->proc_mask_active & mask))
|
|
return; /* No change in state */
|
|
|
|
struct dsp_proc_slot *s = find_proc_slot(dsp, id);
|
|
|
|
if (activate)
|
|
{
|
|
dsp->proc_mask_active |= mask;
|
|
s->mask &= ~NACT_BIT;
|
|
}
|
|
else
|
|
{
|
|
dsp->proc_mask_active &= ~mask;
|
|
s->mask |= NACT_BIT;
|
|
}
|
|
}
|
|
|
|
/* Is the stage specified by the id currently active? */
|
|
bool dsp_proc_active(struct dsp_config *dsp, enum dsp_proc_ids id)
|
|
{
|
|
return (dsp->proc_mask_active & BIT_N(id)) != 0;
|
|
}
|
|
|
|
/* Force the specified stage to receive a format update before the next
|
|
* buffer is sent to process() */
|
|
void dsp_proc_want_format_update(struct dsp_config *dsp,
|
|
enum dsp_proc_ids id)
|
|
{
|
|
struct dsp_proc_slot *s = find_proc_slot(dsp, id);
|
|
|
|
if (s)
|
|
s->version = 0; /* Set invalid */
|
|
}
|
|
|
|
/* Set or unset in-place operation */
|
|
void dsp_proc_set_in_place(struct dsp_config *dsp, enum dsp_proc_ids id,
|
|
bool in_place)
|
|
{
|
|
struct dsp_proc_slot *s = find_proc_slot(dsp, id);
|
|
|
|
if (!s)
|
|
return;
|
|
|
|
const uint32_t mask = BIT_N(id);
|
|
|
|
if (in_place)
|
|
s->mask |= mask;
|
|
else
|
|
s->mask &= ~mask;
|
|
}
|
|
|
|
/* Determine by the rules if the processing function should be called */
|
|
static NO_INLINE bool dsp_proc_new_format(struct dsp_proc_slot *s,
|
|
struct dsp_config *dsp,
|
|
struct dsp_buffer *buf)
|
|
{
|
|
struct dsp_proc_entry *this = &s->proc_entry;
|
|
struct sample_format *format = &buf->format;
|
|
|
|
switch (proc_db_entry(s)->configure(
|
|
this, dsp, DSP_PROC_NEW_FORMAT, (intptr_t)format))
|
|
{
|
|
case PROC_NEW_FORMAT_OK:
|
|
s->version = format->version;
|
|
return true;
|
|
|
|
case PROC_NEW_FORMAT_TRANSITION:
|
|
return true;
|
|
|
|
case PROC_NEW_FORMAT_DEACTIVATED:
|
|
s->version = format->version;
|
|
return false;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static FORCE_INLINE void dsp_proc_call(struct dsp_proc_slot *s,
|
|
struct dsp_config *dsp,
|
|
struct dsp_buffer **buf_p)
|
|
{
|
|
struct dsp_buffer *buf = *buf_p;
|
|
|
|
if (UNLIKELY(buf->format.version != s->version))
|
|
{
|
|
if (!dsp_proc_new_format(s, dsp, buf))
|
|
return;
|
|
}
|
|
|
|
if (s->mask)
|
|
{
|
|
if ((s->mask & (buf->proc_mask | NACT_BIT)) || buf->remcount <= 0)
|
|
return;
|
|
|
|
buf->proc_mask |= s->mask;
|
|
}
|
|
|
|
s->proc_entry.process(&s->proc_entry, buf_p);
|
|
}
|
|
|
|
/**
|
|
* dsp_process:
|
|
*
|
|
* Process and convert src audio to dst based on the DSP configuration.
|
|
* dsp: the DSP instance in use
|
|
*
|
|
* src:
|
|
* remcount = number of input samples remaining; set to desired
|
|
* number of samples to be processed
|
|
* pin[0] = left channel if non-interleaved, audio data if
|
|
* interleaved or mono
|
|
* pin[1] = right channel if non-interleaved, ignored if
|
|
* interleaved or mono
|
|
* proc_mask = set to zero on first call, updated by this function
|
|
* to keep track of which in-place stages have been
|
|
* run on the buffers to avoid multiple applications of
|
|
* them
|
|
* format = for internal buffers, gives the relevant format
|
|
* details
|
|
*
|
|
* dst:
|
|
* remcount = number of samples placed in buffer so far; set to
|
|
* zero on first call
|
|
* p16out = current fill pointer in destination buffer; set to
|
|
* buffer start on first call
|
|
* bufcount = remaining buffer space in samples; set to maximum
|
|
* desired output count on first call
|
|
* format = ignored
|
|
*
|
|
* Processing stops when src is exhausted or dst is filled, whichever
|
|
* happens first. Samples can still be output when src buffer is empty
|
|
* if samples are held internally. Generally speaking, continue calling
|
|
* until no data is consumed and no data is produced to purge the DSP
|
|
* to the maximum extent feasible. Some internal processing stages may
|
|
* require more input before more output can be generated, thus there
|
|
* is no guarantee the DSP is free of data awaiting processing at that
|
|
* point.
|
|
*
|
|
* Additionally, samples consumed and samples produced do not necessarily
|
|
* have a direct correlation. Samples may be consumed without producing
|
|
* any output and samples may be produced without consuming any input.
|
|
* It depends on which stages are actively processing data at the time
|
|
* of the call and how they function internally.
|
|
*/
|
|
void dsp_process(struct dsp_config *dsp, struct dsp_buffer *src,
|
|
struct dsp_buffer *dst)
|
|
{
|
|
if (dst->bufcount <= 0)
|
|
{
|
|
/* No place to put anything thus nothing may be safely consumed */
|
|
return;
|
|
}
|
|
|
|
DSP_PROCESS_START();
|
|
|
|
/* Tag input with codec-specified sample format */
|
|
src->format = dsp->io_data.format;
|
|
|
|
if (src->format.version != dsp->io_data.sample_buf.format.version)
|
|
dsp_sample_input_format_change(&dsp->io_data, &src->format);
|
|
|
|
while (1)
|
|
{
|
|
/* Out-of-place-processing stages take the current buf as input
|
|
* and switch the buffer to their own output buffer */
|
|
struct dsp_buffer *buf = src;
|
|
|
|
/* Convert input samples to internal format */
|
|
dsp->io_data.input_samples(&dsp->io_data, &buf);
|
|
|
|
/* Call all active/enabled stages depending if format is
|
|
same/changed on the last output buffer */
|
|
for (struct dsp_proc_slot *s = dsp->proc_slots; s; s = s->next)
|
|
dsp_proc_call(s, dsp, &buf);
|
|
|
|
/* Don't overread/write src/destination */
|
|
int outcount = MIN(dst->bufcount, buf->remcount);
|
|
|
|
if (outcount <= 0)
|
|
break; /* Output full or purged internal buffers */
|
|
|
|
if (UNLIKELY(buf->format.version != dsp->io_data.output_version))
|
|
dsp_sample_output_format_change(&dsp->io_data, &buf->format);
|
|
|
|
dsp->io_data.outcount = outcount;
|
|
dsp->io_data.output_samples(&dsp->io_data, buf, dst);
|
|
|
|
/* Advance buffers by what output consumed and produced */
|
|
dsp_advance_buffer32(buf, outcount);
|
|
dsp_advance_buffer_output(dst, outcount);
|
|
|
|
DSP_PROCESS_LOOP();
|
|
} /* while */
|
|
|
|
DSP_PROCESS_END();
|
|
}
|
|
|
|
intptr_t dsp_configure(struct dsp_config *dsp, unsigned int setting,
|
|
intptr_t value)
|
|
{
|
|
return proc_broadcast(dsp, setting, value);
|
|
}
|
|
|
|
struct dsp_config *dsp_get_config(unsigned int dsp_id)
|
|
{
|
|
if (dsp_id >= DSP_COUNT)
|
|
return NULL;
|
|
|
|
return &dsp_conf[dsp_id];
|
|
}
|
|
|
|
/* Return the id given a dsp pointer (or even via something within
|
|
the struct itself) */
|
|
unsigned int dsp_get_id(const struct dsp_config *dsp)
|
|
{
|
|
return dsp - dsp_conf;
|
|
}
|
|
|
|
/* Do what needs initializing before enable/disable calls can be made.
|
|
* Must be done before changing settings for the first time. */
|
|
void dsp_init(void)
|
|
{
|
|
static const uint8_t slot_count[DSP_COUNT] INITDATA_ATTR =
|
|
{
|
|
[CODEC_IDX_AUDIO] = DSP_NUM_PROC_STAGES,
|
|
[CODEC_IDX_VOICE] = DSP_VOICE_NUM_PROC_STAGES
|
|
};
|
|
|
|
for (unsigned int i = 0, count, shift = 0;
|
|
i < DSP_COUNT;
|
|
i++, shift += count)
|
|
{
|
|
struct dsp_config *dsp = &dsp_conf[i];
|
|
|
|
count = slot_count[i];
|
|
dsp->slot_free_mask = MASK_N(uint32_t, count, shift);
|
|
|
|
dsp_sample_io_init(&dsp->io_data, i);
|
|
|
|
/* Enable misc handler by default for the audio DSP */
|
|
if (i == CODEC_IDX_AUDIO)
|
|
dsp_proc_enable(dsp, DSP_PROC_MISC_HANDLER, true);
|
|
|
|
/* Call global init for DSPs that need it */
|
|
for (unsigned int j = 0; j < ARRAYLEN(dsp_init_fn); ++j)
|
|
dsp_init_fn[j](dsp, i);
|
|
|
|
/*
|
|
* FIXME: This is a no-op and should not be needed, but it
|
|
* appears there is a race condition here that causes
|
|
* prefetch abort panics on the Clip+. See FS#13386.
|
|
* Replacing the loop with mdelay(1) also suppresses
|
|
* the crash.
|
|
*/
|
|
for (unsigned int j = 0; j < DSP_NUM_PROC_STAGES; j++)
|
|
dsp_proc_database[j]->configure(NULL, dsp, DSP_INIT, i);
|
|
|
|
dsp_configure(dsp, DSP_RESET, 0);
|
|
}
|
|
}
|