#pragma once #include "AudioToolsConfig.h" #include "AudioTools/AudioLibs/I2SCodecStream.h" #include "AudioTools/CoreAudio/AudioActions.h" namespace audio_tools { /** * @brief New functionality which replaces the AudioKitStream that is based on * the legacy AudioKit library. This functionality uses the new * arduino-audio-driver library! It is the same as I2SCodecStream extended by * some AudioActions and some method calls to determine defined pin values. * See https://github.com/pschatzmann/arduino-audio-driver * @ingroup io * @author Phil Schatzmann * @copyright GPLv3 */ class AudioBoardStream : public I2SCodecStream { struct AudioBoardAction : public AudioActions::Action { AudioBoardAction(AudioBoard &board, AudioDriverKey key) { this->key = key; this->p_board = &board; } AudioDriverKey key; AudioBoard *p_board; int id() override { return key | 0x400; } bool readValue() override { return p_board->isKeyPressed(key); } }; public: /** * @brief Default constructor: for available AudioBoard values check * the audioboard variables in * https://pschatzmann.github.io/arduino-audio-driver/html/group__audio__driver.html * Further information can be found in * https://github.com/pschatzmann/arduino-audio-driver/wiki */ AudioBoardStream(audio_driver::AudioBoard &board) : I2SCodecStream(board) { // pin mode already set up by driver library actions.setPinMode(false); } bool begin() override { return I2SCodecStream::begin(); } bool begin(I2SCodecConfig cfg) override { return I2SCodecStream::begin(cfg); } /** * @brief Process input keys and pins * */ void processActions() { // TRACED(); actions.processActions(); delay(1); } /** * @brief Defines a new action that is executed when the Button is pressed */ void addAction(AudioDriverKey key, void (*action)(bool, int, void *), void *ref = nullptr) { AudioBoardAction *abo = new AudioBoardAction(board(), key); abo->actionOn = action; abo->ref = (ref == nullptr) ? this : ref; actions.add(*abo); } /** * @brief Defines a new action that is executed when the Button is pressed and released */ void addAction(AudioDriverKey key, void (*actionOn)(bool, int, void *), void (*actionOff)(bool, int, void *), void *ref = nullptr) { AudioBoardAction *abo = new AudioBoardAction(board(), key); abo->actionOn = actionOn; abo->actionOn = actionOff; abo->ref = (ref == nullptr) ? this : ref; actions.add(*abo); } /** * @brief Defines a new action that is executed when the indicated pin is * active * * @param pin * @param action * @param ref */ void addAction(int pin, void (*action)(bool, int, void *), void *ref = nullptr) { TRACEI(); // determine logic from config AudioActions::ActiveLogic activeLogic = getActionLogic(pin); actions.add(pin, action, activeLogic, ref == nullptr ? this : ref); } /** * @brief Defines a new action that is executed when the indicated pin is * active * * @param pin * @param action * @param activeLogic * @param ref */ void addAction(int pin, void (*action)(bool, int, void *), AudioActions::ActiveLogic activeLogic, void *ref = nullptr) { TRACEI(); actions.add(pin, action, activeLogic, ref == nullptr ? this : ref); } /// Provides access to the AudioActions AudioActions &audioActions() { return actions; } AudioActions &getActions() { return actions; } /** * @brief Relative volume control * * @param vol */ void incrementVolume(float inc) { float current_volume = getVolume(); float new_volume = current_volume + inc; LOGI("incrementVolume: %f -> %f", current_volume, new_volume); setVolume(new_volume); } /** * @brief Increase the volume * */ static void actionVolumeUp(bool, int, void *ref) { TRACEI(); AudioBoardStream *self = (AudioBoardStream *)ref; self->incrementVolume(+self->actionVolumeIncrementValue()); } /** * @brief Decrease the volume * */ static void actionVolumeDown(bool, int, void *ref) { TRACEI(); AudioBoardStream *self = (AudioBoardStream *)ref; self->incrementVolume(-self->actionVolumeIncrementValue()); } /** * @brief Toggle start stop * */ static void actionStartStop(bool, int, void *ref) { TRACEI(); AudioBoardStream *self = (AudioBoardStream *)ref; self->active = !self->active; self->setActive(self->active); } /** * @brief Start * */ static void actionStart(bool, int, void *ref) { TRACEI(); AudioBoardStream *self = (AudioBoardStream *)ref; self->active = true; self->setActive(self->active); } /** * @brief Stop */ static void actionStop(bool, int, void *ref) { TRACEI(); AudioBoardStream *self = (AudioBoardStream *)ref; self->active = false; self->setActive(self->active); } /** * @brief Switch off the PA if the headphone in plugged in * and switch it on again if the headphone is unplugged. * This method complies with the */ static void actionHeadphoneDetection(bool, int, void *ref) { AudioBoardStream *self = (AudioBoardStream *)ref; if (self->pinHeadphoneDetect() >= 0) { // detect changes bool isConnected = self->headphoneStatus(); if (self->headphoneIsConnected != isConnected) { self->headphoneIsConnected = isConnected; // update if things have stabilized bool powerActive = !isConnected; LOGW("Headphone jack has been %s", isConnected ? "inserted" : "removed"); self->setSpeakerActive(powerActive); } } delay(1); } /** * @brief Get the gpio number for auxin detection * * @return -1 non-existent * Others gpio number */ GpioPin pinAuxin() { return getPinID(PinFunction::AUXIN_DETECT); } /** * @brief Get the gpio number for headphone detection * * @return -1 non-existent * Others gpio number */ GpioPin pinHeadphoneDetect() { return getPinID(PinFunction::HEADPHONE_DETECT); } /** * @brief Get the gpio number for PA enable * * @return -1 non-existent * Others gpio number */ GpioPin pinPaEnable() { return getPinID(PinFunction::PA); } // /** // * @brief Get the gpio number for adc detection // * // * @return -1 non-existent // * Others gpio number // */ // GpioPin pinAdcDetect() { return getPin(AUXIN_DETECT); } /** * @brief Get the record-button id for adc-button * * @return -1 non-existent * Others button id */ GpioPin pinInputRec() { return getPinID(PinFunction::KEY, 1); } /** * @brief Get the number for mode-button * * @return -1 non-existent * Others number */ GpioPin pinInputMode() { return getPinID(PinFunction::KEY, 2); } /** * @brief Get number for set function * * @return -1 non-existent * Others number */ GpioPin pinInputSet() { return getPinID(PinFunction::KEY, 4); } /** * @brief Get number for play function * * @return -1 non-existent * Others number */ GpioPin pinInputPlay() { return getPinID(PinFunction::KEY, 3); } /** * @brief number for volume up function * * @return -1 non-existent * Others number */ GpioPin pinVolumeUp() { return getPinID(PinFunction::KEY, 6); } /** * @brief Get number for volume down function * * @return -1 non-existent * Others number */ GpioPin pinVolumeDown() { return getPinID(PinFunction::KEY, 5); } /** * @brief Get LED pin * * @return -1 non-existent * Others gpio number */ GpioPin pinLed(int idx) { return getPinID(PinFunction::LED, idx); } /// the same as setPAPower() void setSpeakerActive(bool active) { setPAPower(active); } /** * @brief Returns true if the headphone was detected * * @return true * @return false */ bool headphoneStatus() { int headphoneGpioPin = pinHeadphoneDetect(); return headphoneGpioPin > 0 ? !digitalRead(headphoneGpioPin) : false; } /** * @brief The oposite of setMute(): setActive(true) calls setMute(false) */ void setActive(bool active) { setMute(!active); } /// add start/stop on inputMode void addStartStopAction() { // pin conflicts for pinInputMode() with the SD CS pin for AIThinker and // buttons int sd_cs = getSdCsPin(); int input_mode = pinInputMode(); if (input_mode != -1 && (input_mode != sd_cs || !cfg.sd_active)) { LOGD("actionInputMode") addAction(input_mode, actionStartStop); } } /// add volume up and volume down action void addVolumeActions() { // pin conflicts with SD Lyrat SD CS GpioPin and buttons / Conflict on // Audiokit V. 2957 int sd_cs = getSdCsPin(); int vol_up = pinVolumeUp(); int vol_down = pinVolumeDown(); if ((vol_up != -1 && vol_down != -1) && (!cfg.sd_active || (vol_down != sd_cs && vol_up != sd_cs))) { LOGD("actionVolumeDown") addAction(vol_down, actionVolumeDown); LOGD("actionVolumeUp") addAction(vol_up, actionVolumeUp); } else { LOGW("Volume Buttons ignored because of conflict: %d ", pinVolumeDown()); } } /// Adds headphone determination void addHeadphoneDetectionAction() { // pin conflicts with AIThinker A101: key6 and headphone detection int head_phone = pinHeadphoneDetect(); if (head_phone != -1 && (getPinID(PinFunction::KEY, 6) != head_phone)) { actions.add(head_phone, actionHeadphoneDetection, AudioActions::ActiveChange, this); } } /** * @brief Setup the supported default actions (volume, start/stop, headphone * detection) */ void addDefaultActions() { TRACEI(); addHeadphoneDetectionAction(); addStartStopAction(); addVolumeActions(); } /// Defines the increment value used by actionVolumeDown/actionVolumeUp void setActionVolumeIncrementValue(float value) { action_increment_value = value; } float actionVolumeIncrementValue() { return action_increment_value; } bool isKeyPressed(int key) { if (!board()) return false; return board().isKeyPressed(key); } protected: AudioActions actions; bool headphoneIsConnected = false; bool active = true; float action_increment_value = 0.02; int getSdCsPin() { static GpioPin sd_cs = -2; // execute only once if (sd_cs != -2) return sd_cs; auto sd_opt = getPins().getSPIPins(PinFunction::SD); if (sd_opt) { sd_cs = sd_opt.value().cs; } else { // no spi -> no sd LOGI("No sd defined -> sd_active=false") cfg.sd_active = false; sd_cs = -1; } return sd_cs; } /// Determines the action logic (ActiveLow or ActiveTouch) for the pin AudioActions::ActiveLogic getActionLogic(int pin) { auto opt = board().getPins().getPin(pin); PinLogic logic = PinLogic::Input; if (opt) logic = opt.value().pin_logic; switch (logic) { case PinLogic::Input: case PinLogic::InputActiveLow: return AudioActions::ActiveLow; case PinLogic::InputActiveHigh: return AudioActions::ActiveHigh; case PinLogic::InputActiveTouch: return AudioActions::ActiveTouch; default: return AudioActions::ActiveLow; } } }; } // namespace audio_tools