Config save/load works.
This commit is contained in:
		
							parent
							
								
									e5cb26e581
								
							
						
					
					
						commit
						db52d728a3
					
				|  | @ -16,7 +16,6 @@ add_executable(ploopy_headphones | |||
|     i2s.c | ||||
|     fix16.c | ||||
|     bqf.c | ||||
|     user.c | ||||
|     configuration_manager.c | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,6 +25,12 @@ | |||
| 
 | ||||
| #include "bqf.h" | ||||
| 
 | ||||
| int filter_stages = 0; | ||||
| 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]; | ||||
| 
 | ||||
| /**
 | ||||
|  * Configure a low-pass filter. Parameters are as follows: | ||||
|  * | ||||
|  |  | |||
|  | @ -41,6 +41,16 @@ typedef struct _bqf_mem_t { | |||
|     fix16_t y_2; | ||||
| } bqf_mem_t; | ||||
| 
 | ||||
| // 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[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]; | ||||
| 
 | ||||
| #define Q_BUTTERWORTH 0.707106781 | ||||
| #define Q_BESSEL 0.577350269 | ||||
| #define Q_LINKWITZ_RILEY 0.5 | ||||
|  | @ -58,5 +68,4 @@ void bqf_highshelf_config(double, double, double, double, bqf_coeff_t *); | |||
| fix16_t bqf_transform(fix16_t, bqf_coeff_t *, bqf_mem_t *); | ||||
| void bqf_memreset(bqf_mem_t *); | ||||
| 
 | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -17,16 +17,16 @@ | |||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <inttypes.h> | ||||
| #include "pico/stdlib.h" | ||||
| #include "pico/usb_device.h" | ||||
| #include <stdbool.h> | ||||
| #include "configuration_manager.h" | ||||
| #include "configuration_types.h" | ||||
| 
 | ||||
| // TODO: Duplicated from os_descriptors.h
 | ||||
| #define U16_HIGH(_u16)          ((uint8_t) (((_u16) >> 8) & 0x00ff)) | ||||
| #define U16_LOW(_u16)           ((uint8_t) ((_u16)       & 0x00ff)) | ||||
| 
 | ||||
| #define U16_TO_U8S_LE(_u16)     U16_LOW(_u16), U16_HIGH(_u16) | ||||
| #include "bqf.h" | ||||
| #include "run.h" | ||||
| #ifndef TEST_TARGET | ||||
| #include "pico/stdlib.h" | ||||
| #include "pico/usb_device.h" | ||||
| #include "hardware/flash.h" | ||||
| #endif | ||||
| 
 | ||||
| /**
 | ||||
|  * We have multiple copies of the device configuration. This is the factory | ||||
|  | @ -44,72 +44,71 @@ | |||
|  * and becomes the new user configuration.  | ||||
|  */ | ||||
| static const default_configuration default_config = { | ||||
|     .set_configuration = { SET_CONFIGURATION, sizeof(default_config) }, | ||||
|     .filters = { | ||||
|         .filter = { FILTER_CONFIGURATION, sizeof(default_config.filters) }, | ||||
|         .f1 = {PEAKING,     38,   -19,  0.9}, | ||||
|         .f2 = {LOWSHELF,    2900,   2,  0.7}, | ||||
|         .f3 = {PEAKING,     430,    3,  3.5}, | ||||
|         .f4 = {HIGHSHELF,   8400,   2,  0.7}, | ||||
|         .f5 = {PEAKING,     4800,   3,    5} | ||||
|         .f1 = { PEAKING,    38,   -19,  0.9 }, | ||||
|         .f2 = { LOWSHELF,   2900,   2,  0.7 }, | ||||
|         .f3 = { PEAKING,    430,    3,  3.5 }, | ||||
|         .f4 = { HIGHSHELF,  8400,   2,  0.7 }, | ||||
|         .f5 = { PEAKING,    4800,   3,    5 } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // Grab the last 4k page of flash for our configuration strutures.
 | ||||
| #ifndef TEST_TARGET | ||||
| static const size_t USER_CONFIGURATION_OFFSET = PICO_FLASH_SIZE_BYTES - 0x1000; | ||||
| static const uint8_t *user_configuration = (const uint8_t *) (XIP_BASE + USER_CONFIGURATION_OFFSET); | ||||
| #endif | ||||
| /**
 | ||||
|  * TODO: For now, assume we always get a complete configuration but maybe we | ||||
|  * should handle merging configurations where, for example, only a new | ||||
|  * filter_configuration_tlv was received. | ||||
|  */ | ||||
| static uint8_t working_configuration[256]; | ||||
| static uint8_t working_configuration[2][256]; | ||||
| static uint8_t inactive_working_configuration = 0; | ||||
| static uint8_t result_buffer[256] = { U16_TO_U8S_LE(NOK), U16_TO_U8S_LE(4) }; | ||||
| 
 | ||||
| static bool config_dirty = false; | ||||
| static bool reload_config = false; | ||||
| static uint16_t write_offset = 0; | ||||
| static uint16_t read_offset = 0; | ||||
| 
 | ||||
| bool validate_filter_configuration(filter_configuration_tlv *filters) | ||||
| { | ||||
|     if (filters->header.type != FILTER_CONFIGURATION) | ||||
|     { | ||||
|     if (filters->header.type != FILTER_CONFIGURATION) { | ||||
|         printf("Error! Not a filter TLV (%x)..\n", filters->header.type); | ||||
|         return false; | ||||
|     } | ||||
|     uint8_t *ptr = (uint8_t *)filters->header.value; | ||||
|     const uint8_t *end = (uint8_t *)filters + filters->header.length; | ||||
|     while ((ptr + 4) < end) | ||||
|     { | ||||
|         uint32_t type = *(uint32_t *)ptr; | ||||
|         uint16_t remaining = (uint16_t)(end - ptr); | ||||
|         printf("Found Filter Type %d (%p rem: %d)..\n", type, ptr, remaining); | ||||
|         switch (type) | ||||
|         { | ||||
|     int count = 0; | ||||
|     while ((ptr + 4) < end) { | ||||
|         const uint32_t type = *(uint32_t *)ptr; | ||||
|         const uint16_t remaining = (uint16_t)(end - ptr); | ||||
|         if (count++ > MAX_FILTER_STAGES) { | ||||
|             printf("Error! Too many filters defined.\n"); | ||||
|             return false; | ||||
|         } | ||||
|         switch (type) { | ||||
|         case LOWPASS: | ||||
|         case HIGHPASS: | ||||
|         case BANDPASSSKIRT: | ||||
|         case BANDPASSPEAK: | ||||
|         case NOTCH: | ||||
|         case ALLPASS: | ||||
|         { | ||||
|             if (remaining < sizeof(filter2)) | ||||
|             { | ||||
|         case ALLPASS: { | ||||
|             if (remaining < sizeof(filter2)) { | ||||
|                 printf("Error! Not enough data left for filter2 (%d)..\n", remaining); | ||||
|                 return false; | ||||
|             } | ||||
|             filter2 *args = (filter2 *)ptr; | ||||
|             printf("Args: F0: %0.2f, Q: %0.2f\n", args->f0, args->Q); | ||||
|             ptr += sizeof(filter2); | ||||
|             break; | ||||
|         } | ||||
|         case PEAKING: | ||||
|         case LOWSHELF: | ||||
|         case HIGHSHELF: | ||||
|         { | ||||
|             if (remaining < sizeof(filter3)) | ||||
|             { | ||||
|         case HIGHSHELF: { | ||||
|             if (remaining < sizeof(filter3)) { | ||||
|                 printf("Error! Not enough data left for filter3 (%d)..\n", remaining); | ||||
|                 return false; | ||||
|             } | ||||
|             filter3 *args = (filter3 *)ptr; | ||||
|             printf("Args: F0: %0.2f, dbGain: %0.2f, Q: %0.2f\n", args->f0, args->dBgain, args->Q); | ||||
|             ptr += sizeof(filter3); | ||||
|             break; | ||||
|         } | ||||
|  | @ -118,17 +117,44 @@ bool validate_filter_configuration(filter_configuration_tlv *filters) | |||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     if (ptr != end) | ||||
|     { | ||||
|     if (ptr != end) { | ||||
|         printf("Error! Did not consume the whole TLV (%p != %p)..\n", ptr, end); | ||||
|         return false; | ||||
|     } | ||||
|     printf("Config looks good..\n"); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool validate_configuration(tlv_header *config) | ||||
| { | ||||
| void apply_filter_configuration(filter_configuration_tlv *filters) { | ||||
|     uint8_t *ptr = (uint8_t *)filters->header.value; | ||||
|     const uint8_t *end = (uint8_t *)filters + filters->header.length; | ||||
|     filter_stages = 0; | ||||
| 
 | ||||
|     while ((ptr + 4) < end) { | ||||
|         const uint32_t type = *(uint32_t *)ptr; | ||||
| 
 | ||||
|         // If you reset the memory, you can hear it when you move the sliders on the UI,
 | ||||
|         // is it perhaps OK to leave these and let the old values drop off over time?
 | ||||
|         //bqf_memreset(&bqf_filters_mem_left[filter_stages]);
 | ||||
|         //bqf_memreset(&bqf_filters_mem_right[filter_stages]);
 | ||||
| 
 | ||||
|         switch (type) { | ||||
|             case LOWPASS: INIT_FILTER2(lowpass); | ||||
|             case HIGHPASS: INIT_FILTER2(highpass); | ||||
|             case BANDPASSSKIRT: INIT_FILTER2(bandpass_skirt); | ||||
|             case BANDPASSPEAK: INIT_FILTER2(bandpass_peak); | ||||
|             case NOTCH: INIT_FILTER2(notch); | ||||
|             case ALLPASS: INIT_FILTER2(allpass); | ||||
|             case PEAKING: INIT_FILTER3(peaking); | ||||
|             case LOWSHELF: INIT_FILTER3(lowshelf); | ||||
|             case HIGHSHELF: INIT_FILTER3(highshelf); | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|         filter_stages++; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool validate_configuration(tlv_header *config) { | ||||
|     if (config->type != SET_CONFIGURATION) { | ||||
|         printf("Unexpcected Config type: %d\n", config->type); | ||||
|         return false; | ||||
|  | @ -137,27 +163,102 @@ bool validate_configuration(tlv_header *config) | |||
|     const uint8_t *end = (uint8_t *)config + config->length; | ||||
|     while (ptr < end) { | ||||
|         tlv_header* tlv = (tlv_header*) ptr; | ||||
|         printf("Found TLV type: %d\n", tlv->type); | ||||
|         if (tlv->type == FILTER_CONFIGURATION) | ||||
|         { | ||||
|             if (!validate_filter_configuration((filter_configuration_tlv*) tlv)) { | ||||
|                 return false; | ||||
|             } | ||||
|         if (tlv->length < 4) { | ||||
|             return false; | ||||
|         } | ||||
|         switch (tlv->type) { | ||||
|             case FILTER_CONFIGURATION: | ||||
|                 if (!validate_filter_configuration((filter_configuration_tlv*) tlv)) { | ||||
|                     return false; | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
|                 // Unknown TLVs are not invalid, just ignored.
 | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         ptr += tlv->length; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void load_config() | ||||
| { | ||||
| bool apply_configuration(tlv_header *config) { | ||||
|     uint8_t *ptr = NULL;  | ||||
|     switch (config->type) | ||||
|     { | ||||
|         case SET_CONFIGURATION: | ||||
|             ptr = (uint8_t *) config->value; | ||||
|             break; | ||||
|         case FLASH_HEADER: { | ||||
|             ptr = (uint8_t *) ((flash_header_tlv*) config)->tlvs; | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|             printf("Unexpcected Config type: %d\n", config->type); | ||||
|             return false; | ||||
|     } | ||||
| 
 | ||||
|     const uint8_t *end = (uint8_t *)config + config->length; | ||||
|     while (ptr < end) { | ||||
|         tlv_header* tlv = (tlv_header*) ptr; | ||||
|         switch (tlv->type) { | ||||
|             case FILTER_CONFIGURATION: | ||||
|                 apply_filter_configuration((filter_configuration_tlv*) tlv); | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|         ptr += tlv->length; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void load_config() { | ||||
| #ifndef TEST_TARGET | ||||
|     // Try to load data from flash
 | ||||
|     if (validate_configuration((tlv_header*) user_configuration)) { | ||||
|         apply_configuration((tlv_header*) user_configuration); | ||||
|         return; | ||||
|     } | ||||
| #endif | ||||
|     // If that is no good, use the default config
 | ||||
|     apply_configuration((tlv_header*) &default_config); | ||||
| } | ||||
| 
 | ||||
| void save_config() | ||||
| { | ||||
|     // Write data to flash
 | ||||
| #ifndef TEST_TARGET | ||||
| bool save_config() { | ||||
|     const uint8_t active_configuration = inactive_working_configuration ? 0 : 1; | ||||
|     tlv_header* config = (tlv_header*) working_configuration[active_configuration]; | ||||
| 
 | ||||
|     if (validate_configuration(config)) { | ||||
|         const size_t config_length = config->length - (size_t)((size_t)config->value - (size_t)config); | ||||
|         // Write data to flash
 | ||||
|         flash_header_tlv flash_header; | ||||
|         flash_header.header.type = FLASH_HEADER; | ||||
|         flash_header.header.length = sizeof(flash_header) + config_length; | ||||
|         flash_header.magic = FLASH_MAGIC; | ||||
|         flash_header.version = CONFIG_VERSION; | ||||
|         flash_range_program(USER_CONFIGURATION_OFFSET, (const uint8_t *) &flash_header, sizeof(flash_header)); | ||||
|         flash_range_program(USER_CONFIGURATION_OFFSET + sizeof(flash_header), config->value, config_length); | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool process_cmd(tlv_header* cmd) { | ||||
|     switch (cmd->type) { | ||||
|         case SET_CONFIGURATION: | ||||
|             if (validate_configuration(cmd)) { | ||||
|                 inactive_working_configuration = inactive_working_configuration ? 0 : 1; | ||||
|                 reload_config = true; | ||||
|                 return true; | ||||
|             } | ||||
|         case SAVE_CONFIGURATION: { | ||||
|             if (cmd->length == 4) { | ||||
|                 return save_config(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| // This callback is called when the client sends a message to the device.
 | ||||
|  | @ -169,19 +270,19 @@ void save_config() | |||
| // buffer with a TLV which we expect the client to read next.
 | ||||
| void config_out_packet(struct usb_endpoint *ep) { | ||||
|     struct usb_buffer *buffer = usb_current_out_packet_buffer(ep); | ||||
|     printf("config_out_packet %d\n", buffer->data_len); | ||||
|     //printf("config_out_packet %d\n", buffer->data_len);
 | ||||
| 
 | ||||
|     memcpy(&working_configuration[write_offset], buffer->data, buffer->data_len); | ||||
|     memcpy(&working_configuration[inactive_working_configuration][write_offset], buffer->data, buffer->data_len); | ||||
|     write_offset += buffer->data_len; | ||||
| 
 | ||||
|     const uint16_t transfer_length = ((tlv_header*) working_configuration)->length; | ||||
|     printf("config_length %d %d\n", transfer_length, write_offset); | ||||
|     if (transfer_length >= write_offset) { | ||||
|     const uint16_t transfer_length = ((tlv_header*) working_configuration[inactive_working_configuration])->length; | ||||
|     //printf("config_length %d %d\n", transfer_length, write_offset);
 | ||||
|     if (write_offset >= transfer_length) { | ||||
|         // Command complete, fill the result buffer
 | ||||
|         tlv_header* result = ((tlv_header*) result_buffer); | ||||
|         write_offset = 0; | ||||
| 
 | ||||
|         if (validate_configuration((tlv_header*) working_configuration)) { | ||||
|         if (process_cmd((tlv_header*) working_configuration[inactive_working_configuration])) { | ||||
|             result->type = OK; | ||||
|             result->length = 4; | ||||
|         } | ||||
|  | @ -202,15 +303,16 @@ void config_out_packet(struct usb_endpoint *ep) { | |||
| void config_in_packet(struct usb_endpoint *ep) { | ||||
|     assert(ep->current_transfer); | ||||
|     struct usb_buffer *buffer = usb_current_in_packet_buffer(ep); | ||||
|     printf("config_in_packet %d\n", buffer->data_len); | ||||
|     //printf("config_in_packet %d\n", buffer->data_len);
 | ||||
|     assert(buffer->data_max >= 3); | ||||
| 
 | ||||
|     const uint16_t transfer_length = ((tlv_header*) result_buffer)->length; | ||||
|     const uint16_t packet_length = MIN(buffer->data_max, transfer_length - read_offset); | ||||
|     memcpy(buffer->data, &result_buffer[read_offset], packet_length); | ||||
|     buffer->data_len = packet_length; | ||||
|     read_offset += packet_length; | ||||
| 
 | ||||
|     if (transfer_length >= read_offset) { | ||||
|     if (read_offset >= transfer_length) { | ||||
|         // Done
 | ||||
|         read_offset = 0; | ||||
| 
 | ||||
|  | @ -222,4 +324,16 @@ void config_in_packet(struct usb_endpoint *ep) { | |||
| 
 | ||||
|     usb_grow_transfer(ep->current_transfer, 1); | ||||
|     usb_packet_done(ep); | ||||
| } | ||||
| } | ||||
| 
 | ||||
| void apply_core0_config() { | ||||
| } | ||||
| 
 | ||||
| void apply_core1_config() { | ||||
|     if (reload_config) { | ||||
|         reload_config = false; | ||||
|         const uint8_t active_configuration = inactive_working_configuration ? 0 : 1; | ||||
|         apply_configuration((tlv_header*) working_configuration[active_configuration]); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
|  | @ -15,7 +15,33 @@ | |||
| #ifndef CONFIGURATION_MANAGER_H | ||||
| #define CONFIGURATION_MANAGER_H | ||||
| struct usb_endpoint; | ||||
| 
 | ||||
| // TODO: Duplicated from os_descriptors.h
 | ||||
| #define U16_HIGH(_u16)          ((uint8_t) (((_u16) >> 8) & 0x00ff)) | ||||
| #define U16_LOW(_u16)           ((uint8_t) ((_u16)       & 0x00ff)) | ||||
| 
 | ||||
| #define U16_TO_U8S_LE(_u16)     U16_LOW(_u16), U16_HIGH(_u16) | ||||
| 
 | ||||
| #define INIT_FILTER2(T) { \ | ||||
|     filter2 *args = (filter2 *)ptr; \ | ||||
|     bqf_##T##_config(SAMPLING_FREQ, args->f0, args->Q, &bqf_filters_left[filter_stages]); \ | ||||
|     bqf_##T##_config(SAMPLING_FREQ, args->f0, args->Q, &bqf_filters_right[filter_stages]); \ | ||||
|     ptr += sizeof(filter2); \ | ||||
|     break; \ | ||||
|     } | ||||
| 
 | ||||
| #define INIT_FILTER3(T) { \ | ||||
|     filter3 *args = (filter3 *)ptr; \ | ||||
|     bqf_##T##_config(SAMPLING_FREQ, args->f0, args->dBgain, args->Q, &bqf_filters_left[filter_stages]); \ | ||||
|     bqf_##T##_config(SAMPLING_FREQ, args->f0, args->dBgain, args->Q, &bqf_filters_right[filter_stages]); \ | ||||
|     ptr += sizeof(filter3); \ | ||||
|     break; \ | ||||
|     } | ||||
| 
 | ||||
| void config_in_packet(struct usb_endpoint *ep); | ||||
| void config_out_packet(struct usb_endpoint *ep); | ||||
| extern void load_config(); | ||||
| extern void apply_core0_config(); | ||||
| extern void apply_core1_config(); | ||||
| 
 | ||||
| #endif // CONFIGURATION_MANAGER_H
 | ||||
|  | @ -16,6 +16,9 @@ | |||
| #define __CONFIGURATION_TYPES_H__ | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| #define FLASH_MAGIC 0x2E8AFEDD | ||||
| #define CONFIG_VERSION 1 | ||||
| 
 | ||||
| enum structure_types { | ||||
|     // Commands/Responses, these are container TLVs. The Value will be a set of TLV structures.
 | ||||
|     OK = 0,                     // Standard response when a command was successful
 | ||||
|  | @ -70,6 +73,13 @@ enum filter_type { | |||
|     HIGHSHELF | ||||
| }; | ||||
| 
 | ||||
| typedef struct __attribute__((__packed__)) _flash_header_tlv { | ||||
|     tlv_header header; | ||||
|     uint32_t magic; | ||||
|     uint32_t version; | ||||
|     const uint8_t tlvs[]; | ||||
| } flash_header_tlv; | ||||
| 
 | ||||
| typedef struct __attribute__((__packed__)) _filter_configuration_tlv { | ||||
|     tlv_header header; | ||||
|     const uint8_t filters[]; | ||||
|  | @ -82,6 +92,7 @@ typedef struct __attribute__((__packed__)) _version_status_tlv { | |||
| } version_status_tlv; | ||||
| 
 | ||||
| typedef struct __attribute__((__packed__)) _default_configuration { | ||||
|     tlv_header set_configuration; | ||||
|     const struct __attribute__((__packed__)) { | ||||
|         tlv_header filter; | ||||
|         filter3 f1; | ||||
|  |  | |||
|  | @ -43,18 +43,12 @@ | |||
| #include "ringbuf.h" | ||||
| #include "i2s.h" | ||||
| #include "bqf.h" | ||||
| #include "user.h" | ||||
| #include "os_descriptors.h" | ||||
| #include "configuration_manager.h" | ||||
| 
 | ||||
| i2s_obj_t i2s_write_obj; | ||||
| static uint8_t *userbuf; | ||||
| 
 | ||||
| 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; | ||||
|     union { | ||||
|  | @ -80,7 +74,9 @@ enum vendor_cmds { | |||
| int main(void) { | ||||
|     setup(); | ||||
| 
 | ||||
|     define_filters(); | ||||
|     // Ask the configuration_manager to load a user config from flash,
 | ||||
|     // or use the defaults.
 | ||||
|     load_config(); | ||||
| 
 | ||||
|     // start second core (called "core 1" in the SDK)
 | ||||
|     multicore_launch_core1(core1_entry); | ||||
|  | @ -127,12 +123,16 @@ 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(); | ||||
|     multicore_fifo_push_blocking(CORE0_READY); | ||||
| 
 | ||||
|     i2s_stream_write(&i2s_write_obj, userbuf, samples * 4); | ||||
| 
 | ||||
|     // keep on truckin'
 | ||||
|     usb_grow_transfer(ep->current_transfer, 1); | ||||
|     usb_packet_done(ep); | ||||
| 
 | ||||
|     // Update filters if required
 | ||||
|     apply_core0_config(); | ||||
| } | ||||
| 
 | ||||
| static void update_volume() | ||||
|  | @ -180,6 +180,12 @@ void core1_entry() { | |||
|         // Signal to core 0 that the data has all been transformed
 | ||||
|         multicore_fifo_push_blocking(CORE1_READY); | ||||
| 
 | ||||
|         // Wait for Core 0 to finish running its filtering before we apply config updates
 | ||||
|         multicore_fifo_pop_blocking(); | ||||
| 
 | ||||
|         // Update filters if required
 | ||||
|         apply_core1_config(); | ||||
| 
 | ||||
|         // 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.
 | ||||
|  |  | |||
|  | @ -1,64 +0,0 @@ | |||
| /**
 | ||||
|  * Copyright 2022 Colin Lam, Ploopy Corporation | ||||
|  * | ||||
|  * 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 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program 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 General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
|  */ | ||||
| 
 | ||||
| #include "user.h" | ||||
| #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. 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. | ||||
|  * 2. You're done! Enjoy the sounds of anything you want. | ||||
|  ****************************************************************************/ | ||||
| 
 | ||||
| void define_filters() { | ||||
|     // First filter.
 | ||||
|     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[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[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[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[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++]); | ||||
| } | ||||
|  | @ -1,35 +0,0 @@ | |||
| /**
 | ||||
|  * Copyright 2022 Colin Lam, Ploopy Corporation | ||||
|  * | ||||
|  * 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 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program 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 General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
|  */ | ||||
| 
 | ||||
| #ifndef USER_H | ||||
| #define USER_H | ||||
| 
 | ||||
| #include "bqf.h" | ||||
| 
 | ||||
| // 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[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); | ||||
| 
 | ||||
| #endif | ||||
|  | @ -8,9 +8,10 @@ add_executable(filter_test | |||
|     filter_test.c | ||||
|     ../code/fix16.c | ||||
|     ../code/bqf.c | ||||
|     ../code/user.c | ||||
|     ../code/configuration_manager.c | ||||
| ) | ||||
| 
 | ||||
| target_compile_definitions(filter_test PRIVATE TEST_TARGET) | ||||
| target_include_directories(filter_test PRIVATE ${CMAKE_SOURCE_DIR}/../code) | ||||
| 
 | ||||
| # TODO: user.c includes run.h to get the definition for SAMPLING_FREQ, but this | ||||
|  |  | |||
|  | @ -2,12 +2,7 @@ | |||
| #include <stdlib.h> | ||||
| #include "bqf.h" | ||||
| #include "fix16.h" | ||||
| #include "user.h" | ||||
| 
 | ||||
| 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]; | ||||
| #include "configuration_manager.h" | ||||
| 
 | ||||
| const char* usage = "Usage: %s INFILE OUTFILE\n\n" | ||||
|     "Reads 16bit stereo PCM data from INFILE, runs it through the Ploopy headphones\n" | ||||
|  | @ -52,7 +47,7 @@ int main(int argc, char* argv[]) | |||
| 
 | ||||
|     // The smaple proccesing code, essentially the same as the
 | ||||
|     // code in the firmware's run.c file.
 | ||||
|     define_filters(); | ||||
|     load_config(); | ||||
| 
 | ||||
|     for (int i = 0; i < samples; i++) | ||||
|     { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 George Norton
						George Norton