From c1f1d9140407757fab16c418eed09f5517c649d7 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 11 Jan 2022 13:58:41 +0000 Subject: [PATCH] FiiO M3K: audio recording Recording works now, although I'm sure there will be a few things that need fine-tuning. A major issue is that writing to the SD card creates noticable interference, which happens on the original firmware too but seems worse under Rockbox. (Since Rockbox waits until RAM fills up before writing data, the interference will only be heard on >50 MiB recordings.) Change-Id: I5561dd9668c3bdd34e92f34ef50848aef8c0b7eb --- apps/keymaps/keymap-fiiom3k.c | 42 +++++- firmware/export/ak4376.h | 4 +- firmware/export/config/fiiom3k.h | 8 +- .../ingenic_x1000/fiiom3k/audiohw-fiiom3k.c | 127 +++++++++++++++++- manual/platform/keymap-fiiom3k.tex | 12 ++ 5 files changed, 183 insertions(+), 10 deletions(-) diff --git a/apps/keymaps/keymap-fiiom3k.c b/apps/keymaps/keymap-fiiom3k.c index a97be0870d..337a2184cd 100644 --- a/apps/keymaps/keymap-fiiom3k.c +++ b/apps/keymaps/keymap-fiiom3k.c @@ -138,6 +138,16 @@ static const struct button_mapping button_context_settings[] = { LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) }; /* button_context_settings */ +static const struct button_mapping button_context_settings_rectrigger[] = { + {ACTION_SETTINGS_INC, BUTTON_RIGHT, BUTTON_NONE}, + {ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_INCBIGSTEP, BUTTON_VOL_UP, BUTTON_NONE}, + {ACTION_SETTINGS_DEC, BUTTON_LEFT, BUTTON_NONE}, + {ACTION_SETTINGS_DECREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_DECBIGSTEP, BUTTON_VOL_DOWN, BUTTON_NONE}, + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; /* button_context_settings_rectrigger */ + static const struct button_mapping button_context_settings_eq[] = { {ACTION_SETTINGS_INC, BUTTON_RIGHT, BUTTON_NONE}, {ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, @@ -203,6 +213,33 @@ static const struct button_mapping button_context_yesnoscreen[] = { LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) }; /* button_context_yesnoscreen */ +static const struct button_mapping button_context_recscreen[] = { + {ACTION_REC_PAUSE, BUTTON_SELECT, BUTTON_NONE}, + {ACTION_REC_PAUSE, BUTTON_PLAY, BUTTON_NONE}, + {ACTION_REC_NEWFILE, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT}, + {ACTION_REC_NEWFILE, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY}, + {ACTION_STD_MENU, BUTTON_MENU, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_BACK, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_POWER, BUTTON_NONE}, + {ACTION_STD_PREV, BUTTON_UP, BUTTON_NONE}, + {ACTION_STD_PREVREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_STD_NEXT, BUTTON_DOWN, BUTTON_NONE}, + {ACTION_STD_NEXTREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_STD_PREV, BUTTON_SCROLL_BACK, BUTTON_NONE}, + {ACTION_STD_PREVREPEAT, BUTTON_SCROLL_BACK|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_STD_NEXT, BUTTON_SCROLL_FWD, BUTTON_NONE}, + {ACTION_STD_NEXTREPEAT, BUTTON_SCROLL_FWD|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_INC, BUTTON_VOL_UP, BUTTON_NONE}, + {ACTION_SETTINGS_INCREPEAT, BUTTON_VOL_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_DEC, BUTTON_VOL_DOWN, BUTTON_NONE}, + {ACTION_SETTINGS_DECREPEAT, BUTTON_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_INC, BUTTON_RIGHT, BUTTON_NONE}, + {ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_DEC, BUTTON_LEFT, BUTTON_NONE}, + {ACTION_SETTINGS_DECREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + LAST_ITEM_IN_LIST +}; /* button_context_recscreen */ + static const struct button_mapping button_context_keyboard[] = { {ACTION_KBD_UP, BUTTON_UP, BUTTON_NONE}, {ACTION_KBD_DOWN, BUTTON_DOWN, BUTTON_NONE}, @@ -315,8 +352,9 @@ const struct button_mapping* get_context_mapping(int context) return button_context_list; case CONTEXT_SETTINGS: case CONTEXT_SETTINGS_TIME: - case CONTEXT_SETTINGS_RECTRIGGER: return button_context_settings; + case CONTEXT_SETTINGS_RECTRIGGER: + return button_context_settings_rectrigger; case CONTEXT_SETTINGS_EQ: case CONTEXT_SETTINGS_COLOURCHOOSER: return button_context_settings_eq; @@ -326,6 +364,8 @@ const struct button_mapping* get_context_mapping(int context) return button_context_pitchscreen; case CONTEXT_YESNOSCREEN: return button_context_yesnoscreen; + case CONTEXT_RECSCREEN: + return button_context_recscreen; case CONTEXT_KEYBOARD: return button_context_keyboard; case CONTEXT_USB_HID: diff --git a/firmware/export/ak4376.h b/firmware/export/ak4376.h index 0ae156bc37..ad842b2b80 100644 --- a/firmware/export/ak4376.h +++ b/firmware/export/ak4376.h @@ -22,7 +22,9 @@ #ifndef __AK4376_H__ #define __AK4376_H__ -#define AUDIOHW_CAPS (FILTER_ROLL_OFF_CAP|POWER_MODE_CAP) +/* The target config must define this; defining it here would prevent + the target from supporting audio recording via an alternate codec. */ +/* #define AUDIOHW_CAPS (FILTER_ROLL_OFF_CAP|POWER_MODE_CAP) */ #define AUDIOHW_HAVE_SHORT2_ROLL_OFF #define AK4376_MIN_VOLUME (-890) diff --git a/firmware/export/config/fiiom3k.h b/firmware/export/config/fiiom3k.h index 45c2150208..ea97d52d76 100644 --- a/firmware/export/config/fiiom3k.h +++ b/firmware/export/config/fiiom3k.h @@ -55,11 +55,15 @@ /* Codec / audio hardware defines */ #define HW_SAMPR_CAPS SAMPR_CAP_ALL_192 +#define REC_SAMPR_CAPS (SAMPR_CAP_ALL_96 & ~SAMPR_CAP_64) +#define INPUT_SRC_CAPS SRC_CAP_MIC +#define AUDIOHW_CAPS (FILTER_ROLL_OFF_CAP|POWER_MODE_CAP|MIC_GAIN_CAP) +#define HAVE_RECORDING #define HAVE_AK4376 +#define HAVE_X1000_ICODEC_REC #define HAVE_SW_TONE_CONTROLS #define HAVE_SW_VOLUME_CONTROL - -/* TODO: Need to implement recording */ +#define DEFAULT_REC_MIC_GAIN 12 /* Button defines */ #define CONFIG_KEYPAD FIIO_M3K_PAD diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c index f7dced8f54..9374d23a81 100644 --- a/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c +++ b/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2021 Aidan MacDonald + * Copyright (C) 2021-2022 Aidan MacDonald * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,6 +20,7 @@ ****************************************************************************/ #include "audiohw.h" +#include "audio.h" #include "system.h" #include "pcm_sampr.h" #include "aic-x1000.h" @@ -27,6 +28,11 @@ #include "gpio-x1000.h" #include "logf.h" +static int cur_audio_source = AUDIO_SRC_PLAYBACK; +static int cur_vol_r = AK4376_MIN_VOLUME; +static int cur_vol_l = AK4376_MIN_VOLUME; +static int cur_recvol = 0; +static int cur_filter_roll_off = 0; static int cur_fsel = HW_FREQ_48; static int cur_power_mode = SOUND_HIGH_POWER; @@ -60,29 +66,138 @@ void audiohw_postinit(void) void audiohw_close(void) { - ak4376_close(); + if(cur_audio_source == AUDIO_SRC_PLAYBACK) + ak4376_close(); + else if(cur_audio_source == AUDIO_SRC_MIC) + x1000_icodec_close(); +} + +void audio_set_output_source(int source) +{ + /* this is a no-op */ + (void)source; +} + +void audio_input_mux(int source, unsigned flags) +{ + (void)flags; + + if(source == cur_audio_source) + return; + + /* close whatever codec is currently in use */ + audiohw_close(); + aic_enable_i2s_bit_clock(false); + + /* switch to new source */ + cur_audio_source = source; + + if(source == AUDIO_SRC_PLAYBACK) { + /* power on DAC */ + aic_set_external_codec(true); + aic_set_i2s_mode(AIC_I2S_MASTER_MODE); + ak4376_open(); + + /* apply the old settings */ + audiohw_set_volume(cur_vol_l, cur_vol_r); + audiohw_set_filter_roll_off(cur_filter_roll_off); + set_ak_freqmode(); + } else if(source == AUDIO_SRC_MIC) { + aic_set_external_codec(false); + aic_set_i2s_mode(AIC_I2S_SLAVE_MODE); + /* Note: Sampling frequency is irrelevant here */ + aic_set_i2s_clock(X1000_CLK_EXCLK, 48000, 0); + aic_enable_i2s_bit_clock(true); + + x1000_icodec_open(); + + /* configure the mic */ + x1000_icodec_mic1_enable(true); + x1000_icodec_mic1_bias_enable(true); + x1000_icodec_mic1_configure(JZCODEC_MIC1_DIFFERENTIAL | + JZCODEC_MIC1_BIAS_2_08V); + + /* configure the ADC */ + x1000_icodec_adc_mic_sel(JZCODEC_MIC_SEL_ANALOG); + x1000_icodec_adc_highpass_filter(true); + x1000_icodec_adc_frequency(cur_fsel); + x1000_icodec_adc_enable(true); + + /* configure the mixer to put mic input in both channels */ + x1000_icodec_mixer_enable(true); + x1000_icodec_write(JZCODEC_MIX2, 0x10); + + /* set gain and unmute */ + audiohw_set_recvol(cur_recvol, 0, AUDIO_GAIN_MIC); + x1000_icodec_adc_mute(false); + } else { + logf("bad audio input source: %d (flags: %x)", source, flags); + } } void audiohw_set_volume(int vol_l, int vol_r) { - ak4376_set_volume(vol_l, vol_r); + cur_vol_l = vol_l; + cur_vol_r = vol_r; + + if(cur_audio_source == AUDIO_SRC_PLAYBACK) + ak4376_set_volume(vol_l, vol_r); } void audiohw_set_filter_roll_off(int val) { - ak4376_set_filter_roll_off(val); + cur_filter_roll_off = val; + + if(cur_audio_source == AUDIO_SRC_PLAYBACK) + ak4376_set_filter_roll_off(val); } void audiohw_set_frequency(int fsel) { cur_fsel = fsel; - set_ak_freqmode(); + + if(cur_audio_source == AUDIO_SRC_PLAYBACK) + set_ak_freqmode(); + else + x1000_icodec_adc_frequency(fsel); } void audiohw_set_power_mode(int mode) { cur_power_mode = mode; - set_ak_freqmode(); + + if(cur_audio_source == AUDIO_SRC_PLAYBACK) + set_ak_freqmode(); +} + +static int round_step_up(int x, int step) +{ + int rem = x % step; + if(rem > 0) + rem -= step; + return x - rem; +} + +void audiohw_set_recvol(int left, int right, int type) +{ + (void)right; + + if(type == AUDIO_GAIN_MIC) { + cur_recvol = left; + + if(cur_audio_source == AUDIO_SRC_MIC) { + int mic_gain = round_step_up(left, X1000_ICODEC_MIC_GAIN_STEP); + mic_gain = MIN(mic_gain, X1000_ICODEC_MIC_GAIN_MAX); + mic_gain = MAX(mic_gain, X1000_ICODEC_MIC_GAIN_MIN); + + int adc_gain = left - mic_gain; + adc_gain = MIN(adc_gain, X1000_ICODEC_ADC_GAIN_MAX); + adc_gain = MAX(adc_gain, X1000_ICODEC_ADC_GAIN_MIN); + + x1000_icodec_adc_gain(adc_gain); + x1000_icodec_mic1_gain(mic_gain); + } + } } void ak4376_set_pdn_pin(int level) diff --git a/manual/platform/keymap-fiiom3k.tex b/manual/platform/keymap-fiiom3k.tex index ecf07bebd4..95c56f13ed 100644 --- a/manual/platform/keymap-fiiom3k.tex +++ b/manual/platform/keymap-fiiom3k.tex @@ -45,6 +45,18 @@ \newcommand{\ActionWpsAbSetBNextDir}{Long \ButtonDown} \newcommand{\ActionWpsAbReset}{Long \ButtonSelect} +%Button actions, recording context +\newcommand{\ActionRecPause}{\ButtonSelect{} or \ButtonPlay} +\newcommand{\ActionRecExit}{\ButtonBack{} or \ButtonPower} +\newcommand{\ActionRecNewfile}{Long \ButtonSelect{} or Long \ButtonPlay} +\newcommand{\ActionRecMenu}{\ButtonMenu} +\newcommand{\ActionRecPrev}{\ActionStdPrev} +\newcommand{\ActionRecPrevRepeat}{\ActionStdPrevRepeat} +\newcommand{\ActionRecNext}{\ActionStdNext} +\newcommand{\ActionRecNextRepeat}{\ActionStdNextRepeat} +\newcommand{\ActionRecSettingsInc}{\ButtonVolUp{} or \ButtonRight} +\newcommand{\ActionRecSettingsDec}{\ButtonVolDown{} or \ButtonLeft} + %Button actions, tree context \newcommand{\ActionTreeWps}{Long \ButtonBack} \newcommand{\ActionTreeStop}{Long \ButtonPlay}