/*************************************************************************** * __________ __ ___. * 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); dsp_configure(dsp, DSP_RESET, 0); } }