PCM3060 volume control (#13)
* Initial set of changes to offload the volume controls to the DAC. * Minor cleanup. Added Independent Left/Right volume control. * Code cleanup.
This commit is contained in:
parent
a8f9b522de
commit
47b94b8d73
|
@ -46,15 +46,21 @@
|
|||
i2s_obj_t i2s_write_obj;
|
||||
static uint8_t *userbuf;
|
||||
|
||||
bqf_coeff_t bqf_filters_left[FILTER_STAGES];
|
||||
bqf_coeff_t bqf_filters_right[FILTER_STAGES];
|
||||
bqf_mem_t bqf_filters_mem_left[FILTER_STAGES];
|
||||
bqf_mem_t bqf_filters_mem_right[FILTER_STAGES];
|
||||
bqf_coeff_t bqf_filters_left[MAX_FILTER_STAGES];
|
||||
bqf_coeff_t bqf_filters_right[MAX_FILTER_STAGES];
|
||||
bqf_mem_t bqf_filters_mem_left[MAX_FILTER_STAGES];
|
||||
bqf_mem_t bqf_filters_mem_right[MAX_FILTER_STAGES];
|
||||
|
||||
static struct {
|
||||
uint32_t freq;
|
||||
int16_t volume;
|
||||
int16_t vol_mul;
|
||||
union {
|
||||
int16_t volume[2];
|
||||
int32_t _volume;
|
||||
};
|
||||
union {
|
||||
int16_t target_volume[2];
|
||||
int32_t _target_volume;
|
||||
};
|
||||
bool mute;
|
||||
} audio_state = {
|
||||
.freq = 48000,
|
||||
|
@ -92,7 +98,6 @@ static void _as_audio_packet(struct usb_endpoint *ep) {
|
|||
struct usb_buffer *usb_buffer = usb_current_out_packet_buffer(ep);
|
||||
int16_t *in = (int16_t *) usb_buffer->data;
|
||||
int32_t *out = (int32_t *) userbuf;
|
||||
uint16_t vol_mul = audio_state.vol_mul;
|
||||
int samples = usb_buffer->data_len / 2;
|
||||
|
||||
for (int i = 0; i < samples; i++)
|
||||
|
@ -101,7 +106,7 @@ static void _as_audio_packet(struct usb_endpoint *ep) {
|
|||
multicore_fifo_push_blocking(CORE0_READY);
|
||||
multicore_fifo_push_blocking(samples);
|
||||
|
||||
for (int j = 0; j < FILTER_STAGES; j++) {
|
||||
for (int j = 0; j < filter_stages; j++) {
|
||||
// Left channel filter
|
||||
for (int i = 0; i < samples; i += 2) {
|
||||
fix16_t x_f16 = fix16_from_int((int16_t) out[i]);
|
||||
|
@ -116,10 +121,6 @@ static void _as_audio_packet(struct usb_endpoint *ep) {
|
|||
// Block until core 1 has finished transforming the data
|
||||
uint32_t ready = multicore_fifo_pop_blocking();
|
||||
|
||||
// Multiply the outgoing signal with the volume multiple
|
||||
for (int i = 0; i < samples; i++)
|
||||
out[i] = out[i] * (int32_t) vol_mul;
|
||||
|
||||
i2s_stream_write(&i2s_write_obj, userbuf, samples * 4);
|
||||
|
||||
// keep on truckin'
|
||||
|
@ -127,6 +128,23 @@ static void _as_audio_packet(struct usb_endpoint *ep) {
|
|||
usb_packet_done(ep);
|
||||
}
|
||||
|
||||
static void update_volume()
|
||||
{
|
||||
if (audio_state._volume != audio_state._target_volume) {
|
||||
// PCM3060 volume attenuation:
|
||||
// 0: 0db (default)
|
||||
// 55: -100db
|
||||
// 56..: Mute
|
||||
uint8_t buf[3];
|
||||
buf[0] = 65; // register addr
|
||||
buf[1] = 255 + (audio_state.target_volume[0] / 128); // data left
|
||||
buf[2] = 255 + (audio_state.target_volume[1] / 128); // data right
|
||||
i2c_write_blocking(i2c0, PCM_I2C_ADDR, buf, 3, false);
|
||||
|
||||
audio_state._volume = audio_state._target_volume;
|
||||
}
|
||||
}
|
||||
|
||||
void core1_entry() {
|
||||
uint8_t *userbuf = (uint8_t *) multicore_fifo_pop_blocking();
|
||||
int32_t *out = (int32_t *) userbuf;
|
||||
|
@ -141,7 +159,7 @@ void core1_entry() {
|
|||
|
||||
uint32_t limit = multicore_fifo_pop_blocking();
|
||||
|
||||
for (int j = 0; j < FILTER_STAGES; j++) {
|
||||
for (int j = 0; j < filter_stages; j++) {
|
||||
for (int i = 1; i < limit; i += 2) {
|
||||
fix16_t x_f16 = fix16_from_int((int16_t) out[i]);
|
||||
|
||||
|
@ -154,6 +172,11 @@ void core1_entry() {
|
|||
|
||||
// Signal to core 0 that the data has all been transformed
|
||||
multicore_fifo_push_blocking(CORE1_READY);
|
||||
|
||||
// Update the volume if required. We do this from core1 as
|
||||
// core0 is more heavily loaded, doing this from core0 can
|
||||
// lead to audio crackling.
|
||||
update_volume();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +208,10 @@ void setup() {
|
|||
gpio_set_dir(PCM3060_RST_PIN, GPIO_OUT);
|
||||
gpio_put(PCM3060_RST_PIN, true);
|
||||
|
||||
i2c_init(i2c0, 50000);
|
||||
// The PCM3060 supports standard mode (100kbps) or fast mode (400kbps)
|
||||
// we run in fast mode so we dont block the core for too long while
|
||||
// updating the volume.
|
||||
i2c_init(i2c0, 400000);
|
||||
gpio_set_function(PCM3060_SDA_PIN, GPIO_FUNC_I2C);
|
||||
gpio_set_function(PCM3060_SCL_PIN, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(PCM3060_SDA_PIN);
|
||||
|
@ -203,6 +229,11 @@ void setup() {
|
|||
// Don't remove this. Don't do it.
|
||||
sleep_ms(200);
|
||||
|
||||
// Set data format to 16 bit right justified, MSB first
|
||||
buf[0] = 67; // register addr
|
||||
buf[1] = 0x03; // data
|
||||
i2c_write_blocking(i2c0, PCM_I2C_ADDR, buf, 2, false);
|
||||
|
||||
// Enable DAC
|
||||
buf[0] = 64; // register addr
|
||||
buf[1] = 0xE0; // data
|
||||
|
@ -312,9 +343,9 @@ static const audio_device_config ad_conf = {
|
|||
.bSourceID = 1,
|
||||
.bControlSize = 1,
|
||||
.bmaControls = {
|
||||
AUDIO_FEATURE_MUTE | AUDIO_FEATURE_VOLUME,
|
||||
0,
|
||||
0
|
||||
AUDIO_FEATURE_MUTE, // Master channel
|
||||
AUDIO_FEATURE_VOLUME, // Left channel
|
||||
AUDIO_FEATURE_VOLUME, // Right channel
|
||||
},
|
||||
.iFeature = 0,
|
||||
},
|
||||
|
@ -498,7 +529,16 @@ static bool do_get_current(struct usb_setup_packet *setup) {
|
|||
}
|
||||
case 2: { // volume
|
||||
/* Current volume. See UAC Spec 1.0 p.77 */
|
||||
usb_start_tiny_control_in_transfer(audio_state.volume, 2);
|
||||
const uint8_t cn = (uint8_t) setup->wValue;
|
||||
if (cn == AUDIO_CHANNEL_LEFT_FRONT) {
|
||||
usb_start_tiny_control_in_transfer(audio_state.target_volume[0], 2);
|
||||
}
|
||||
else if (cn == AUDIO_CHANNEL_RIGHT_FRONT) {
|
||||
usb_start_tiny_control_in_transfer(audio_state.target_volume[1], 2);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -512,22 +552,6 @@ static bool do_get_current(struct usb_setup_packet *setup) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// todo this seemed like aood guess, but is not correct
|
||||
uint16_t db_to_vol[91] = {
|
||||
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0002, 0x0002,
|
||||
0x0002, 0x0002, 0x0003, 0x0003, 0x0004, 0x0004, 0x0005, 0x0005,
|
||||
0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000d, 0x000e,
|
||||
0x0010, 0x0012, 0x0014, 0x0017, 0x001a, 0x001d, 0x0020, 0x0024,
|
||||
0x0029, 0x002e, 0x0033, 0x003a, 0x0041, 0x0049, 0x0052, 0x005c,
|
||||
0x0067, 0x0074, 0x0082, 0x0092, 0x00a4, 0x00b8, 0x00ce, 0x00e7,
|
||||
0x0104, 0x0124, 0x0147, 0x016f, 0x019c, 0x01ce, 0x0207, 0x0246,
|
||||
0x028d, 0x02dd, 0x0337, 0x039b, 0x040c, 0x048a, 0x0518, 0x05b7,
|
||||
0x066a, 0x0732, 0x0813, 0x090f, 0x0a2a, 0x0b68, 0x0ccc, 0x0e5c,
|
||||
0x101d, 0x1214, 0x1449, 0x16c3, 0x198a, 0x1ca7, 0x2026, 0x2413,
|
||||
0x287a, 0x2d6a, 0x32f5, 0x392c, 0x4026, 0x47fa, 0x50c3, 0x5a9d,
|
||||
0x65ac, 0x7214, 0x7fff
|
||||
};
|
||||
|
||||
static bool do_get_minimum(struct usb_setup_packet *setup) {
|
||||
if ((setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK) == USB_REQ_TYPE_RECIPIENT_INTERFACE) {
|
||||
switch (setup->wValue >> 8u) {
|
||||
|
@ -583,30 +607,48 @@ static void _audio_reconfigure() {
|
|||
}
|
||||
}
|
||||
|
||||
static void audio_set_volume(int16_t volume) {
|
||||
audio_state.volume = volume;
|
||||
// todo interpolate
|
||||
volume += CENTER_VOLUME_INDEX * 256;
|
||||
if (volume < 0)
|
||||
volume = 0;
|
||||
if (volume >= count_of(db_to_vol) * 256)
|
||||
volume = count_of(db_to_vol) * 256 - 1;
|
||||
audio_state.vol_mul = db_to_vol[((uint16_t)volume) >> 8u];
|
||||
static void audio_set_volume(int8_t channel, int16_t volume) {
|
||||
// volume is in the range 127.9961dB (0x7FFF) .. -127.9961dB (0x8001). 0x8000 = mute
|
||||
// the old code reported a min..max volume of -90.9961dB (0xA500) .. 0dB (0x0)
|
||||
|
||||
if (volume == 0x8000) {
|
||||
// Mute case
|
||||
}
|
||||
else if (volume > (int16_t) MAX_VOLUME) {
|
||||
volume = MAX_VOLUME;
|
||||
}
|
||||
else if (volume < (int16_t) MIN_VOLUME) {
|
||||
volume = MIN_VOLUME;
|
||||
}
|
||||
if (channel == AUDIO_CHANNEL_LEFT_FRONT || channel == 0) {
|
||||
audio_state.target_volume[0] = volume;
|
||||
}
|
||||
if (channel == AUDIO_CHANNEL_RIGHT_FRONT || channel == 0) {
|
||||
audio_state.target_volume[1] = volume;
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_cmd_packet(struct usb_endpoint *ep) {
|
||||
assert(audio_control_cmd_t.cmd == AUDIO_REQ_SetCurrent);
|
||||
struct usb_buffer *buffer = usb_current_out_packet_buffer(ep);
|
||||
|
||||
// printf("%s: CMD: %u, Type: %u, CS: %u, CN: %u, Unit: %u, Len: %u\n", __PRETTY_FUNCTION__, audio_control_cmd_t.cmd, audio_control_cmd_t.type,
|
||||
// audio_control_cmd_t.cs, audio_control_cmd_t.cn, audio_control_cmd_t.unit, audio_control_cmd_t.len);
|
||||
|
||||
audio_control_cmd_t.cmd = 0;
|
||||
if (buffer->data_len >= audio_control_cmd_t.len) {
|
||||
if (audio_control_cmd_t.type == USB_REQ_TYPE_RECIPIENT_INTERFACE) {
|
||||
switch (audio_control_cmd_t.cs) {
|
||||
case 1: { // mute
|
||||
audio_state.mute = buffer->data[0];
|
||||
uint8_t buf[2];
|
||||
buf[0] = 68; // register addr
|
||||
buf[1] = buffer->data[0] ? 0x3 : 0x0; // data
|
||||
i2c_write_blocking(i2c0, PCM_I2C_ADDR, buf, 2, false);
|
||||
break;
|
||||
}
|
||||
case 2: { // volume
|
||||
audio_set_volume(*(int16_t *) buffer->data);
|
||||
audio_set_volume(audio_control_cmd_t.cn, *(int16_t *) buffer->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -747,7 +789,7 @@ void usb_sound_card_init() {
|
|||
assert(device);
|
||||
|
||||
device->setup_request_handler = ad_setup_request_handler;
|
||||
audio_set_volume(DEFAULT_VOLUME);
|
||||
audio_set_volume(0, DEFAULT_VOLUME);
|
||||
_audio_reconfigure();
|
||||
|
||||
usb_device_start();
|
||||
|
|
|
@ -40,10 +40,10 @@
|
|||
|
||||
#define ENCODE_DB(x) ((uint16_t)(int16_t)((x)*256))
|
||||
|
||||
#define MIN_VOLUME ENCODE_DB(-CENTER_VOLUME_INDEX)
|
||||
#define MIN_VOLUME ENCODE_DB(-100)
|
||||
#define DEFAULT_VOLUME ENCODE_DB(0)
|
||||
#define MAX_VOLUME ENCODE_DB(count_of(db_to_vol)-CENTER_VOLUME_INDEX)
|
||||
#define VOLUME_RESOLUTION ENCODE_DB(1)
|
||||
#define MAX_VOLUME ENCODE_DB(0)
|
||||
#define VOLUME_RESOLUTION ENCODE_DB(0.5f)
|
||||
|
||||
typedef struct _audio_device_config {
|
||||
struct usb_configuration_descriptor descriptor;
|
||||
|
@ -125,7 +125,7 @@ static bool do_get_minimum(struct usb_setup_packet *);
|
|||
static bool do_get_maximum(struct usb_setup_packet *);
|
||||
static bool do_get_resolution(struct usb_setup_packet *);
|
||||
static void _audio_reconfigure(void);
|
||||
static void audio_set_volume(int16_t);
|
||||
static void audio_set_volume(int8_t, int16_t);
|
||||
static void audio_cmd_packet(struct usb_endpoint *);
|
||||
static bool as_set_alternate(struct usb_interface *, uint);
|
||||
static bool do_set_current(struct usb_setup_packet *);
|
||||
|
|
|
@ -19,47 +19,46 @@
|
|||
#include "bqf.h"
|
||||
#include "run.h"
|
||||
|
||||
int filter_stages = 0;
|
||||
|
||||
/*****************************************************************************
|
||||
* Here is where your digital signal processing journey begins. Follow this
|
||||
* guide, and don't forget any steps!
|
||||
*
|
||||
* 1. Go to user.h and change FILTER_STAGES to the number of filter stages you
|
||||
* want.
|
||||
* 2. Define the filters that you want to use. Check out "bqf.c" for a
|
||||
* 1. Define the filters that you want to use. Check out "bqf.c" for a
|
||||
* complete list of what they are and how they work. Using those filters, you
|
||||
* can create ANY digital signal shape you want. Anything you can dream of.
|
||||
* 3. You're done! Enjoy the sounds of anything you want.
|
||||
* 2. You're done! Enjoy the sounds of anything you want.
|
||||
****************************************************************************/
|
||||
|
||||
void define_filters() {
|
||||
|
||||
// First filter.
|
||||
bqf_memreset(&bqf_filters_mem_left[0]);
|
||||
bqf_memreset(&bqf_filters_mem_right[0]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 38.0, -19.0, 0.9, &bqf_filters_left[0]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 38.0, -19.0, 0.9, &bqf_filters_right[0]);
|
||||
bqf_memreset(&bqf_filters_mem_left[filter_stages]);
|
||||
bqf_memreset(&bqf_filters_mem_right[filter_stages]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 38.0, -19.0, 0.9, &bqf_filters_left[filter_stages]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 38.0, -19.0, 0.9, &bqf_filters_right[filter_stages++]);
|
||||
|
||||
// Second filter.
|
||||
bqf_memreset(&bqf_filters_mem_left[1]);
|
||||
bqf_memreset(&bqf_filters_mem_right[1]);
|
||||
bqf_lowshelf_config(SAMPLING_FREQ, 2900.0, 3.0, 4.0, &bqf_filters_left[1]);
|
||||
bqf_lowshelf_config(SAMPLING_FREQ, 2900.0, 3.0, 4.0, &bqf_filters_right[1]);
|
||||
bqf_memreset(&bqf_filters_mem_left[filter_stages]);
|
||||
bqf_memreset(&bqf_filters_mem_right[filter_stages]);
|
||||
bqf_lowshelf_config(SAMPLING_FREQ, 2900.0, 3.0, 4.0, &bqf_filters_left[filter_stages]);
|
||||
bqf_lowshelf_config(SAMPLING_FREQ, 2900.0, 3.0, 4.0, &bqf_filters_right[filter_stages++]);
|
||||
|
||||
// Third filter.
|
||||
bqf_memreset(&bqf_filters_mem_left[2]);
|
||||
bqf_memreset(&bqf_filters_mem_right[2]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 430.0, 6.0, 3.5, &bqf_filters_left[2]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 430.0, 6.0, 3.5, &bqf_filters_right[2]);
|
||||
|
||||
bqf_memreset(&bqf_filters_mem_left[filter_stages]);
|
||||
bqf_memreset(&bqf_filters_mem_right[filter_stages]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 430.0, 6.0, 3.5, &bqf_filters_left[filter_stages]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 430.0, 6.0, 3.5, &bqf_filters_right[filter_stages++]);
|
||||
|
||||
// Fourth filter.
|
||||
bqf_memreset(&bqf_filters_mem_left[3]);
|
||||
bqf_memreset(&bqf_filters_mem_right[3]);
|
||||
bqf_highshelf_config(SAMPLING_FREQ, 8400.0, 3.0, 4.0, &bqf_filters_left[3]);
|
||||
bqf_highshelf_config(SAMPLING_FREQ, 8400.0, 3.0, 4.0, &bqf_filters_right[3]);
|
||||
bqf_memreset(&bqf_filters_mem_left[filter_stages]);
|
||||
bqf_memreset(&bqf_filters_mem_right[filter_stages]);
|
||||
bqf_highshelf_config(SAMPLING_FREQ, 8400.0, 3.0, 4.0, &bqf_filters_left[filter_stages]);
|
||||
bqf_highshelf_config(SAMPLING_FREQ, 8400.0, 3.0, 4.0, &bqf_filters_right[filter_stages++]);
|
||||
|
||||
// Fifth filter.
|
||||
bqf_memreset(&bqf_filters_mem_left[4]);
|
||||
bqf_memreset(&bqf_filters_mem_right[4]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 4800.0, 6.0, 5.0, &bqf_filters_left[4]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 4800.0, 6.0, 5.0, &bqf_filters_right[4]);
|
||||
bqf_memreset(&bqf_filters_mem_left[filter_stages]);
|
||||
bqf_memreset(&bqf_filters_mem_right[filter_stages]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 4800.0, 6.0, 5.0, &bqf_filters_left[filter_stages]);
|
||||
bqf_peaking_config(SAMPLING_FREQ, 4800.0, 6.0, 5.0, &bqf_filters_right[filter_stages++]);
|
||||
}
|
||||
|
|
|
@ -20,13 +20,15 @@
|
|||
|
||||
#include "bqf.h"
|
||||
|
||||
// todo fix this. people will forget this.
|
||||
#define FILTER_STAGES 5 // Don't forget to set this to the right size!
|
||||
// In reality we do not have enough CPU resource to run 8 filtering
|
||||
// stages without some optimisation.
|
||||
#define MAX_FILTER_STAGES 8
|
||||
extern int filter_stages;
|
||||
|
||||
extern bqf_coeff_t bqf_filters_left[FILTER_STAGES];
|
||||
extern bqf_coeff_t bqf_filters_right[FILTER_STAGES];
|
||||
extern bqf_mem_t bqf_filters_mem_left[FILTER_STAGES];
|
||||
extern bqf_mem_t bqf_filters_mem_right[FILTER_STAGES];
|
||||
extern bqf_coeff_t bqf_filters_left[MAX_FILTER_STAGES];
|
||||
extern bqf_coeff_t bqf_filters_right[MAX_FILTER_STAGES];
|
||||
extern bqf_mem_t bqf_filters_mem_left[MAX_FILTER_STAGES];
|
||||
extern bqf_mem_t bqf_filters_mem_right[MAX_FILTER_STAGES];
|
||||
|
||||
void define_filters(void);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ This is a basic utility for testing the Ploopy headphones filtering routines on
|
|||
Find a source file and use ffmpeg to convert it to 16bit stereo PCM samples:
|
||||
|
||||
```
|
||||
ffmpeg -i <input file> -map 0:6 -vn -f s16le -acodec pcm_s16le input.pcm
|
||||
ffmpeg -i <input file> -vn -f s16le -acodec pcm_s16le input.pcm
|
||||
```
|
||||
|
||||
Run `filter_test` to process the PCM samples. The `filter_test` program takes two arguments an input file and an output file:
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
#include "fix16.h"
|
||||
#include "user.h"
|
||||
|
||||
bqf_coeff_t bqf_filters_left[FILTER_STAGES];
|
||||
bqf_coeff_t bqf_filters_right[FILTER_STAGES];
|
||||
bqf_mem_t bqf_filters_mem_left[FILTER_STAGES];
|
||||
bqf_mem_t bqf_filters_mem_right[FILTER_STAGES];
|
||||
bqf_coeff_t bqf_filters_left[MAX_FILTER_STAGES];
|
||||
bqf_coeff_t bqf_filters_right[MAX_FILTER_STAGES];
|
||||
bqf_mem_t bqf_filters_mem_left[MAX_FILTER_STAGES];
|
||||
bqf_mem_t bqf_filters_mem_right[MAX_FILTER_STAGES];
|
||||
|
||||
const char* usage = "Usage: %s INFILE OUTFILE\n\n"
|
||||
"Reads 16bit stereo PCM data from INFILE, runs it through the Ploopy headphones\n"
|
||||
|
@ -59,7 +59,7 @@ int main(int argc, char* argv[])
|
|||
out[i] = in[i];
|
||||
}
|
||||
|
||||
for (int j = 0; j < FILTER_STAGES; j++)
|
||||
for (int j = 0; j < filter_stages; j++)
|
||||
{
|
||||
for (int i = 0; i < samples; i ++)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue