diff --git a/examples/BA1_TGA_Pro_demo/BA1_TGA_Pro_demo.ino b/examples/BA1_TGA_Pro_demo/BA1_TGA_Pro_demo.ino index d4d19d1..c66cf6c 100644 --- a/examples/BA1_TGA_Pro_demo/BA1_TGA_Pro_demo.ino +++ b/examples/BA1_TGA_Pro_demo/BA1_TGA_Pro_demo.ino @@ -21,7 +21,8 @@ using namespace midi; //#define ENABLE_MEM_TEST // uncomment this line and 'Save As' to a new location to test the SPI memory -using namespace BAGuitar; +using namespace BALibrary; +using namespace BAEffects; AudioInputI2S i2sIn; AudioOutputI2S i2sOut; diff --git a/examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino b/examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino index 2649ae1..8e1211f 100644 --- a/examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino +++ b/examples/BA2_TGA_Pro_1MEM/BA2_TGA_Pro_1MEM.ino @@ -23,7 +23,8 @@ MIDI_CREATE_DEFAULT_INSTANCE(); using namespace midi; -using namespace BAGuitar; +using namespace BALibrary; +using namespace BAEffects; AudioInputI2S i2sIn; AudioOutputI2S i2sOut; diff --git a/examples/BA3_TGA_Pro_2MEM/BA3_TGA_Pro_2MEM.ino b/examples/BA3_TGA_Pro_2MEM/BA3_TGA_Pro_2MEM.ino index 49a83a5..40f0adb 100644 --- a/examples/BA3_TGA_Pro_2MEM/BA3_TGA_Pro_2MEM.ino +++ b/examples/BA3_TGA_Pro_2MEM/BA3_TGA_Pro_2MEM.ino @@ -24,7 +24,8 @@ MIDI_CREATE_DEFAULT_INSTANCE(); using namespace midi; -using namespace BAGuitar; +using namespace BAEffects; +using namespace BALibrary; AudioInputI2S i2sIn; AudioOutputI2S i2sOut; diff --git a/examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino b/examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino index 3cc7190..0eb2353 100644 --- a/examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino +++ b/examples/BA4_TGA_Pro_delay_reverb/BA4_TGA_Pro_delay_reverb.ino @@ -14,7 +14,8 @@ #include #include "BAGuitar.h" -using namespace BAGuitar; +using namespace BAEffects; +using namespace BALibrary; BAAudioControlWM8731 codecControl; diff --git a/examples/BA5_TGA_Pro_ExternalDelay_demo/BA5_TGA_Pro_ExternalDelay_demo.ino b/examples/BA5_TGA_Pro_ExternalDelay_demo/BA5_TGA_Pro_ExternalDelay_demo.ino index 717a9a5..1f3618f 100644 --- a/examples/BA5_TGA_Pro_ExternalDelay_demo/BA5_TGA_Pro_ExternalDelay_demo.ino +++ b/examples/BA5_TGA_Pro_ExternalDelay_demo/BA5_TGA_Pro_ExternalDelay_demo.ino @@ -14,7 +14,8 @@ #include "BAGuitar.h" -using namespace BAGuitar; +using namespace BAEffects; +using namespace BALibrary; AudioInputI2S i2sIn; AudioOutputI2S i2sOut; diff --git a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino index bb6a910..d057c5a 100644 --- a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino +++ b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino @@ -2,7 +2,8 @@ #include "BAGuitar.h" using namespace midi; -using namespace BAGuitar; +using namespace BAEffects; +using namespace BALibrary; AudioInputI2S i2sIn; AudioOutputI2S i2sOut; diff --git a/examples/Delay/SoundOnSoundDemo/SoundOnSoundDemo.ino b/examples/Delay/SoundOnSoundDemo/SoundOnSoundDemo.ino new file mode 100644 index 0000000..ec22c9a --- /dev/null +++ b/examples/Delay/SoundOnSoundDemo/SoundOnSoundDemo.ino @@ -0,0 +1,191 @@ +/************************************************************************* + * This demo uses the BAGuitar library to provide enhanced control of + * the TGA Pro board. + * + * The latest copy of the BA Guitar library can be obtained from + * https://github.com/Blackaddr/BAGuitar + * + * This demo will provide an audio passthrough, as well as exercise the + * MIDI interface. + * + * It can optionally exercise the SPI MEM0 if installed on the TGA Pro board. + * + */ +#include +#include +#include +#include +#include "BAGuitar.h" + +#include +static const unsigned sUsbTransportBufferSize = 16; +typedef midi::UsbTransport UsbTransport; + +UsbTransport sUsbTransport; + +MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, uMIDI); + + +MIDI_CREATE_DEFAULT_INSTANCE(); +using namespace midi; + +using namespace BAEffects; + +#define MIDI_DEBUG + +using namespace BALibrary; + +AudioInputI2S i2sIn; +AudioOutputI2S i2sOut; +BAAudioControlWM8731 codec; + +// External SRAM is required for this effect due to the very long +// delays required. +ExternalSramManager externalSram; +ExtMemSlot delaySlot; // Declare an external memory slot. + +AudioEffectSOS sos(&delaySlot); + +// Add some effects for our soloing channel +AudioEffectDelay delayModule; // we'll add a little slapback echo +AudioMixer4 gainModule; // This will be used simply to reduce the gain before the reverb +AudioEffectReverb reverb; // Add a bit of 'verb to our tone +AudioFilterBiquad cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab. +AudioMixer4 mixer; + +// Connect the input +AudioConnection inputToSos(i2sIn, 0, sos, 0); +AudioConnection inputToSolo(i2sIn, 0, delayModule, 0); + +// Patch cables for the SOLO channel +AudioConnection inputToGain(delayModule, 0, gainModule, 0); +AudioConnection inputToReverb(gainModule, 0, reverb, 0); + +// Output Mixer +AudioConnection mixer0input(i2sIn, 0, mixer, 0); // SOLO Dry Channel +AudioConnection mixer1input(reverb, 0, mixer, 1); // SOLO Wet Channel +AudioConnection mixer2input(sos, 0, mixer, 2); // SOS Channel +AudioConnection inputToCab(mixer, 0, cabFilter, 0); + +// CODEC Outputs +AudioConnection outputLeft(cabFilter, 0, i2sOut, 0); +AudioConnection outputRight(cabFilter, 0, i2sOut, 1); + +int loopCount = 0; + +void OnControlChange(byte channel, byte control, byte value) { + sos.processMidi(channel-1, control, value); + #ifdef MIDI_DEBUG + Serial.print("Control Change, ch="); + Serial.print(channel, DEC); + Serial.print(", control="); + Serial.print(control, DEC); + Serial.print(", value="); + Serial.print(value, DEC); + Serial.println(); + #endif +} + +void setup() { + +delay(100); + Serial.begin(57600); // Start the serial port + + // Disable the codec first + codec.disable(); + delay(100); + AudioMemory(128); + delay(5); + + // Enable the codec + Serial.println("Enabling codec...\n"); + codec.enable(); + delay(100); + + // We have to request memory be allocated to our slot. + externalSram.requestMemory(&delaySlot, SPI_MEM0_SIZE_BYTES, MemSelect::MEM0, true); + //externalSram.requestMemory(&delaySlot, 50.0f, MemSelect::MEM0, true); + + // Setup MIDI + MIDI.begin(MIDI_CHANNEL_OMNI); + MIDI.setHandleControlChange(OnControlChange); + uMIDI.begin(MIDI_CHANNEL_OMNI); + uMIDI.setHandleControlChange(OnControlChange); + + // Configure the LED to indicate the gate status + sos.setGateLedGpio(USR_LED_ID); + + // Configure which MIDI CC's will control the effect parameters + sos.mapMidiControl(AudioEffectSOS::BYPASS,16); + sos.mapMidiControl(AudioEffectSOS::CLEAR_FEEDBACK_TRIGGER,22); + sos.mapMidiControl(AudioEffectSOS::GATE_TRIGGER,23); + sos.mapMidiControl(AudioEffectSOS::GATE_OPEN_TIME,20); + sos.mapMidiControl(AudioEffectSOS::GATE_CLOSE_TIME,21); + sos.mapMidiControl(AudioEffectSOS::FEEDBACK,24); + sos.mapMidiControl(AudioEffectSOS::VOLUME,17); + + // Besure to enable the delay. When disabled, audio is is completely blocked + // to minimize resources to nearly zero. + sos.enable(); + + // Set some default values. + // These can be changed by sending MIDI CC messages over the USB using + // the BAMidiTester application. + sos.bypass(false); + sos.gateOpenTime(3000.0f); + sos.gateCloseTime(1000.0f); + sos.feedback(0.9f); + + // Setup effects on the SOLO channel + gainModule.gain(0, 0.25); // the reverb unit clips easily if the input is too high + delayModule.delay(0, 50.0f); // 50 ms slapback delay + + // Setup 2-stages of LPF, cutoff 4500 Hz, Q-factor 0.7071 (a 'normal' Q-factor) + cabFilter.setLowpass(0, 4500, .7071); + cabFilter.setLowpass(1, 4500, .7071); + + // Setup the Mixer + mixer.gain(0, 0.5f); // SOLO Dry gain + mixer.gain(1, 0.5f); // SOLO Wet gain + mixer.gain(1, 1.0f); // SOS gain + +} + + + +void loop() { + // usbMIDI.read() needs to be called rapidly from loop(). When + // each MIDI messages arrives, it return true. The message must + // be fully processed before usbMIDI.read() is called again. + + if (loopCount % 524288 == 0) { + Serial.print("Processor Usage, Total: "); Serial.print(AudioProcessorUsage()); + Serial.print("% "); + Serial.print(" sos: "); Serial.print(sos.processorUsage()); + Serial.println("%"); + } + loopCount++; + + MIDI.read(); + uMIDI.read(); + +// // check for new MIDI from USB +// if (usbMIDI.read()) { +// // this code entered only if new MIDI received +// byte type, channel, data1, data2, cable; +// type = usbMIDI.getType(); // which MIDI message, 128-255 +// channel = usbMIDI.getChannel(); // which MIDI channel, 1-16 +// data1 = usbMIDI.getData1(); // first data byte of message, 0-127 +// data2 = usbMIDI.getData2(); // second data byte of message, 0-127 +// Serial.println(String("Received a MIDI message on channel ") + channel); +// +// if (type == MidiType::ControlChange) { +// // if type is 3, it's a CC MIDI Message +// // Note: the Arduino MIDI library encodes channels as 1-16 instead +// // of 0 to 15 as it should, so we must subtract one. +// OnControlChange(channel-1, data1, data2); +// } +// } + +} + diff --git a/examples/Delay/SoundOnSoundDemo/name.c b/examples/Delay/SoundOnSoundDemo/name.c new file mode 100644 index 0000000..5ea00fe --- /dev/null +++ b/examples/Delay/SoundOnSoundDemo/name.c @@ -0,0 +1,19 @@ +// To give your project a unique name, this code must be +// placed into a .c file (its own tab). It can not be in +// a .cpp file or your main sketch (the .ino file). + +#include "usb_names.h" + +// Edit these lines to create your own name. The length must +// match the number of characters in your custom name. + +#define MIDI_NAME {'B','l','a','c','k','a','d','d','r',' ','A','u','d','i','o',' ','T','G','A',' ','P','r','o'} +#define MIDI_NAME_LEN 23 + +// Do not change this part. This exact format is required by USB. + +struct usb_string_descriptor_struct usb_string_product_name = { + 2 + MIDI_NAME_LEN * 2, + 3, + MIDI_NAME +}; diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index 49b6e90..e094a8d 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -22,13 +22,13 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H -#define __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H +#ifndef __BAEFFECTS_BAAUDIOEFFECTANALOGDELAY_H +#define __BAEFFECTS_BAAUDIOEFFECTANALOGDELAY_H #include #include "LibBasicFunctions.h" -namespace BAGuitar { +namespace BAEffects { /**************************************************************************//** * AudioEffectAnalogDelay models BBD based analog delays. It provides controls @@ -72,7 +72,7 @@ public: /// Construct an analog delay using external SPI via an ExtMemSlot. The amount of /// delay will be determined by the amount of memory in the slot. /// @param slot A pointer to the ExtMemSlot to use for the delay. - AudioEffectAnalogDelay(ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory + AudioEffectAnalogDelay(BALibrary::ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory virtual ~AudioEffectAnalogDelay(); ///< Destructor @@ -163,11 +163,11 @@ private: bool m_bypass = true; bool m_enable = false; bool m_externalMemory = false; - AudioDelay *m_memory = nullptr; + BALibrary::AudioDelay *m_memory = nullptr; size_t m_maxDelaySamples = 0; audio_block_t *m_previousBlock = nullptr; audio_block_t *m_blockToRelease = nullptr; - IirBiQuadFilterHQ *m_iir = nullptr; + BALibrary::IirBiQuadFilterHQ *m_iir = nullptr; // Controls int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping @@ -185,4 +185,4 @@ private: } -#endif /* __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H */ +#endif /* __BAEFFECTS_BAAUDIOEFFECTANALOGDELAY_H */ diff --git a/src/AudioEffectSOS.h b/src/AudioEffectSOS.h new file mode 100644 index 0000000..d72f5ef --- /dev/null +++ b/src/AudioEffectSOS.h @@ -0,0 +1,146 @@ +/**************************************************************************//** + * @file + * @author Steve Lascos + * @company Blackaddr Audio + * + * AudioEffectSOS is a class f + * + * @copyright 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 . + *****************************************************************************/ + +#ifndef __BAEFFECTS_BAAUDIOEFFECTSOS_H +#define __BAEFFECTS_BAAUDIOEFFECTSOS_H + +#include +#include "LibBasicFunctions.h" + +namespace BAEffects { + +/**************************************************************************//** + * AudioEffectSOS + *****************************************************************************/ +class AudioEffectSOS : public AudioStream { +public: + + ///< List of AudioEffectAnalogDelay MIDI controllable parameters + enum { + BYPASS = 0, ///< controls effect bypass + GATE_TRIGGER, ///< begins the gate sequence + GATE_OPEN_TIME, ///< controls how long it takes to open the gate + //GATE_HOLD_TIME, ///< controls how long the gate stays open at unity + GATE_CLOSE_TIME, ///< controls how long it takes to close the gate (release) + CLEAR_FEEDBACK_TRIGGER, ///< begins the sequence to clear out the looping feedback + FEEDBACK, ///< controls the amount of feedback, more gives longer SOS sustain + VOLUME, ///< controls the output volume level + NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls + }; + + // *** CONSTRUCTORS *** + AudioEffectSOS() = delete; + AudioEffectSOS(float maxDelayMs); + AudioEffectSOS(size_t numSamples); + + /// Construct an analog delay using external SPI via an ExtMemSlot. The amount of + /// delay will be determined by the amount of memory in the slot. + /// @param slot A pointer to the ExtMemSlot to use for the delay. + AudioEffectSOS(BALibrary::ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory + + virtual ~AudioEffectSOS(); ///< Destructor + + void setGateLedGpio(int pinId); + + // *** PARAMETERS *** + void gateOpenTime(float milliseconds); + + void gateCloseTime(float milliseconds); + + void feedback(float feedback) { m_feedback = feedback; } + + /// Bypass the effect. + /// @param byp when true, bypass wil disable the effect, when false, effect is enabled. + /// Note that audio still passes through when bypass is enabled. + void bypass(bool byp) { m_bypass = byp; } + + /// Activate the gate automation. Input gate will open, then close. + void trigger() { m_inputGateAuto.trigger(); } + + /// Set the output volume. This affect both the wet and dry signals. + /// @details The default is 1.0. + /// @param vol Sets the output volume between -1.0 and +1.0 + void volume(float vol) {m_volume = vol; } + + // ** ENABLE / DISABLE ** + + /// Enables audio processing. Note: when not enabled, CPU load is nearly zero. + void enable(); + + /// Disables audio process. When disabled, CPU load is nearly zero. + void disable() { m_enable = false; } + + // ** MIDI ** + + /// Sets whether MIDI OMNI channel is processig on or off. When on, + /// all midi channels are used for matching CCs. + /// @param isOmni when true, all channels are processed, when false, channel + /// must match configured value. + void setMidiOmni(bool isOmni) { m_isOmni = isOmni; } + + /// Configure an effect parameter to be controlled by a MIDI CC + /// number on a particular channel. + /// @param parameter one of the parameter names in the class enum + /// @param midiCC the CC number from 0 to 127 + /// @param midiChannel the effect will only response to the CC on this channel + /// when OMNI mode is off. + void mapMidiControl(int parameter, int midiCC, int midiChannel = 0); + + /// process a MIDI Continous-Controller (CC) message + /// @param channel the MIDI channel from 0 to 15) + /// @param midiCC the CC number from 0 to 127 + /// @param value the CC value from 0 to 127 + void processMidi(int channel, int midiCC, int value); + + virtual void update(void); ///< update automatically called by the Teesny Audio Library + +private: + audio_block_t *m_inputQueueArray[1]; + bool m_isOmni = false; + bool m_bypass = true; + bool m_enable = false; + BALibrary::AudioDelay *m_memory = nullptr; + bool m_externalMemory = true; + audio_block_t *m_previousBlock = nullptr; + audio_block_t *m_blockToRelease = nullptr; + size_t m_maxDelaySamples = 0; + int m_gateLedPinId = -1; + + // Controls + int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping + size_t m_delaySamples = 0; + float m_openTimeMs = 0.0f; + float m_closeTimeMs = 0.0f; + float m_feedback = 0.0f; + float m_volume = 1.0f; + + // Automated Controls + BALibrary::ParameterAutomationSequence m_inputGateAuto = BALibrary::ParameterAutomationSequence(3); + BALibrary::ParameterAutomationSequence m_clearFeedbackAuto = BALibrary::ParameterAutomationSequence(3); + + // Private functions + void m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal); + void m_postProcessing(audio_block_t *out, audio_block_t *input); +}; + +} + +#endif /* __BAEFFECTS_BAAUDIOEFFECTSOS_H */ diff --git a/src/BAAudioControlWM8731.h b/src/BAAudioControlWM8731.h index 18b3de9..6a38797 100644 --- a/src/BAAudioControlWM8731.h +++ b/src/BAAudioControlWM8731.h @@ -22,10 +22,10 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAGUITAR__BAAUDIOCONTROLWM8731_H -#define __BAGUITAR__BAAUDIOCONTROLWM8731_H +#ifndef __BALIBRARY_BAAUDIOCONTROLWM8731_H +#define __BALIBRARY_BAAUDIOCONTROLWM8731_H -namespace BAGuitar { +namespace BALibrary { constexpr int WM8731_NUM_REGS = 10; // Number of registers in the internal shadow array @@ -126,6 +126,6 @@ private: }; -} /* namespace BAGuitar */ +} /* namespace BALibrary */ -#endif /* __BAGUITAR__BAAUDIOCONTROLWM8731_H */ +#endif /* __BALIBRARY_BAAUDIOCONTROLWM8731_H */ diff --git a/src/BAAudioEffectDelayExternal.h b/src/BAAudioEffectDelayExternal.h index 587efbf..9e88b21 100644 --- a/src/BAAudioEffectDelayExternal.h +++ b/src/BAAudioEffectDelayExternal.h @@ -22,15 +22,15 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAGUITAR_BAAUDIOEFFECTDELAYEXTERNAL_H -#define __BAGUITAR_BAAUDIOEFFECTDELAYEXTERNAL_H +#ifndef __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H +#define __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H #include #include "AudioStream.h" #include "BAHardware.h" -namespace BAGuitar { +namespace BAEffects { /**************************************************************************//** * BAAudioEffectDelayExternal can use external SPI RAM for delay rather than @@ -45,12 +45,12 @@ public: /// Specifiy which external memory to use /// @param type specify which memory to use - BAAudioEffectDelayExternal(BAGuitar::MemSelect type); + BAAudioEffectDelayExternal(BALibrary::MemSelect type); /// Specify external memory, and how much of the memory to use /// @param type specify which memory to use /// @param delayLengthMs maximum delay length in milliseconds - BAAudioEffectDelayExternal(BAGuitar::MemSelect type, float delayLengthMs); + BAAudioEffectDelayExternal(BALibrary::MemSelect type, float delayLengthMs); virtual ~BAAudioEffectDelayExternal(); /// set the actual amount of delay on a given delay tap @@ -67,7 +67,7 @@ public: static unsigned m_usingSPICount[2]; // internal use for all instances private: - void initialize(BAGuitar::MemSelect mem, unsigned delayLength = 1e6); + void initialize(BALibrary::MemSelect mem, unsigned delayLength = 1e6); void read(uint32_t address, uint32_t count, int16_t *data); void write(uint32_t address, uint32_t count, const int16_t *data); void zero(uint32_t address, uint32_t count); @@ -79,7 +79,7 @@ private: static unsigned m_allocated[2]; audio_block_t *m_inputQueueArray[1]; - BAGuitar::MemSelect m_mem; + BALibrary::MemSelect m_mem; SPIClass *m_spi = nullptr; int m_spiChannel = 0; int m_misoPin = 0; @@ -92,6 +92,6 @@ private: }; -} /* namespace BAGuitar */ +} /* namespace BAEffects */ -#endif /* __BAGUITAR_BAAUDIOEFFECTDELAYEXTERNAL_H */ +#endif /* __BAEFFECTS_BAAUDIOEFFECTDELAYEXTERNAL_H */ diff --git a/src/BAGpio.h b/src/BAGpio.h index 53ade35..f7766af 100644 --- a/src/BAGpio.h +++ b/src/BAGpio.h @@ -20,12 +20,12 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAGUITAR_BAGPIO_H -#define __BAGUITAR_BAGPIO_H +#ifndef __BALIBRARY_BAGPIO_H +#define __BALIBRARY_BAGPIO_H #include "BAHardware.h" -namespace BAGuitar { +namespace BALibrary { /**************************************************************************//** * BAGpio provides a convince class to easily control the direction and state @@ -71,6 +71,6 @@ private: uint8_t m_ledState; }; -} /* namespace BAGuitar */ +} /* namespace BALibrary */ -#endif /* __BAGUITAR_BAGPIO_H */ +#endif /* __BALIBRARY_BAGPIO_H */ diff --git a/src/BAGuitar.h b/src/BAGuitar.h index 1fc8319..d4c4622 100644 --- a/src/BAGuitar.h +++ b/src/BAGuitar.h @@ -29,6 +29,7 @@ #include "BAGpio.h" #include "BAAudioEffectDelayExternal.h" #include "AudioEffectAnalogDelay.h" +#include "AudioEffectSOS.h" #include "LibBasicFunctions.h" #include "LibMemoryManagement.h" diff --git a/src/BAHardware.h b/src/BAHardware.h index 5637bca..a609731 100644 --- a/src/BAHardware.h +++ b/src/BAHardware.h @@ -20,15 +20,15 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAGUTIAR_BAHARDWARE_H -#define __BAGUTIAR_BAHARDWARE_H +#ifndef __BALIBRARY_BAHARDWARE_H +#define __BALIBRARY_BAHARDWARE_H #include /**************************************************************************//** * BAGuitar is a namespace/Library for Guitar processing from Blackaddr Audio. *****************************************************************************/ -namespace BAGuitar { +namespace BALibrary { // uncomment the line that corresponds to your hardware #define TGA_PRO_REVA @@ -74,11 +74,17 @@ constexpr size_t MEM_MAX_ADDR[NUM_MEM_SLOTS] = { 131071, 131071 }; /**************************************************************************//** * General Purpose SPI Interfaces *****************************************************************************/ -enum SpiDeviceId : unsigned { +enum class SpiDeviceId : unsigned { SPI_DEVICE0 = 0, ///< Arduino SPI device SPI_DEVICE1 = 1 ///< Arduino SPI1 device }; constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip +constexpr size_t SPI_MEM0_SIZE_BYTES = 131072; +constexpr size_t SPI_MEM0_MAX_AUDIO_SAMPLES = SPI_MEM0_SIZE_BYTES/sizeof(int16_t); + +constexpr size_t SPI_MEM1_SIZE_BYTES = 131072; +constexpr size_t SPI_MEM1_MAX_AUDIO_SAMPLES = SPI_MEM1_SIZE_BYTES/sizeof(int16_t); + #else @@ -87,7 +93,7 @@ constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip #endif -} // namespace BAGuitar +} // namespace BALibrary -#endif /* __BAGUTIAR_BAHARDWARE_H */ +#endif /* __BALIBRARY_BAHARDWARE_H */ diff --git a/src/BASpiMemory.h b/src/BASpiMemory.h index d5395bb..92e83fc 100644 --- a/src/BASpiMemory.h +++ b/src/BASpiMemory.h @@ -20,8 +20,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAGUITAR_BASPIMEMORY_H -#define __BAGUITAR_BASPIMEMORY_H +#ifndef __BALIBRARY_BASPIMEMORY_H +#define __BALIBRARY_BASPIMEMORY_H #include #include @@ -29,7 +29,7 @@ #include "BATypes.h" #include "BAHardware.h" -namespace BAGuitar { +namespace BALibrary { /**************************************************************************//** * This wrapper class uses the Arduino SPI (Wire) library to access the SPI ram. @@ -139,35 +139,41 @@ public: /// initialize and configure the SPI peripheral void begin() override; - /// Write a block of 8-bit data to the specified address + /// Write a block of 8-bit data to the specified address. Be check + /// isWriteBusy() before sending the next DMA transfer. /// @param address the address in the SPI RAM to write to /// @param src pointer to the source data block /// @param numBytes size of the data block in bytes void write(size_t address, uint8_t *src, size_t numBytes) override; - /// Write a block of zeros to the specified address + /// Write a block of zeros to the specified address. Be check + /// isWriteBusy() before sending the next DMA transfer. /// @param address the address in the SPI RAM to write to /// @param numBytes size of the data block in bytes void zero(size_t address, size_t numBytes) override; - /// Write a block of 16-bit data to the specified address + /// Write a block of 16-bit data to the specified address. Be check + /// isWriteBusy() before sending the next DMA transfer. /// @param address the address in the SPI RAM to write to /// @param src pointer to the source data block /// @param numWords size of the data block in 16-bit words void write16(size_t address, uint16_t *src, size_t numWords) override; - /// Write a block of 16-bit zeros to the specified address + /// Write a block of 16-bit zeros to the specified address. Be check + /// isWriteBusy() before sending the next DMA transfer. /// @param address the address in the SPI RAM to write to /// @param numWords size of the data block in 16-bit words void zero16(size_t address, size_t numWords) override; - /// Read a block of 8-bit data from the specified address + /// Read a block of 8-bit data from the specified address. Be check + /// isReadBusy() before sending the next DMA transfer. /// @param address the address in the SPI RAM to write to /// @param dest pointer to the destination /// @param numBytes size of the data block in bytes void read(size_t address, uint8_t *dest, size_t numBytes) override; - /// read a block 16-bit data word from the specified address + /// read a block 16-bit data word from the specified address. Be check + /// isReadBusy() before sending the next DMA transfer. /// @param address the address in the SPI RAM to read from /// @param dest the pointer to the destination /// @param numWords the number of 16-bit words to transfer @@ -210,6 +216,6 @@ private: }; -} /* namespace BAGuitar */ +} /* namespace BALibrary */ -#endif /* __BAGUITAR_BASPIMEMORY_H */ +#endif /* __BALIBRARY_BASPIMEMORY_H */ diff --git a/src/BATypes.h b/src/BATypes.h index 294cb42..01bdc1f 100644 --- a/src/BATypes.h +++ b/src/BATypes.h @@ -19,10 +19,10 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAGUITAR_BATYPES_H -#define __BAGUITAR_BATYPES_H +#ifndef __BALIBRARY_BATYPES_H +#define __BALIBRARY_BATYPES_H -namespace BAGuitar { +namespace BALibrary { #define UNUSED(x) (void)(x) @@ -152,7 +152,7 @@ private: const size_t m_maxSize; ///< maximum size of the queue }; -} // BAGuitar +} // BALibrary -#endif /* __BAGUITAR_BATYPES_H */ +#endif /* __BALIBRARY_BATYPES_H */ diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index 0507e43..25439c2 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -30,10 +30,10 @@ #include "BATypes.h" #include "LibMemoryManagement.h" -#ifndef __BAGUITAR_LIBBASICFUNCTIONS_H -#define __BAGUITAR_LIBBASICFUNCTIONS_H +#ifndef __BALIBRARY_LIBBASICFUNCTIONS_H +#define __BALIBRARY_LIBBASICFUNCTIONS_H -namespace BAGuitar { +namespace BALibrary { /**************************************************************************//** * QueuePosition is used for storing the index (in an array of queues) and the @@ -92,9 +92,15 @@ void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, floa /// @param out pointer to output audio block /// @param in pointer to input audio block /// @param vol volume cofficient between -1.0 and +1.0 -/// @param coeffShift number of bits to shiftt the coefficient +/// @param coeffShift number of bits to shift the coefficient void gainAdjust(audio_block_t *out, audio_block_t *in, float vol, int coeffShift = 0); +/// Combine two audio blocks through vector addition +/// out[n] = in0[n] + in1[n] +/// @param out pointer to output audio block +/// @param in0 pointer to first input audio block to combine +/// @param in1 pointer to second input audio block to combine +void combine(audio_block_t *out, audio_block_t *in0, audio_block_t *in1); template class RingBuffer; // forward declare so AudioDelay can use it. @@ -128,7 +134,7 @@ public: /// @param maxDelayTimeMs max length of time you want in the buffer specified in milliseconds AudioDelay(float maxDelayTimeMs); - /// Construct an audio buffer using a slot configured with the BAGuitar::ExternalSramManager + /// Construct an audio buffer using a slot configured with the BALibrary::ExternalSramManager /// @param slot a pointer to the slot representing the memory you wish to use for the buffer. AudioDelay(ExtMemSlot *slot); @@ -147,6 +153,12 @@ public: /// @returns a pointer to the requested audio_block_t audio_block_t *getBlock(size_t index); + /// Returns the max possible delay samples. For INTERNAL memory, the delay can be equal to + /// the full maxValue specified. For EXTERNAL memory, the max delay is actually one audio + /// block less then the full size to prevent wrapping. + /// @returns the maximum delay offset in units of samples. + size_t getMaxDelaySamples(); + /// Retrieve an audio block (or samples) from the buffer. /// @details when using INTERNAL memory, only supported size is AUDIO_BLOCK_SAMPLES. When using /// EXTERNAL, a size smaller than AUDIO_BLOCK_SAMPLES can be requested. @@ -161,6 +173,8 @@ public: /// @returns pointer to the underlying ExtMemSlot. ExtMemSlot *getSlot() const { return m_slot; } + + /// Ween using INTERNAL memory, thsi function can return a pointer to the underlying RingBuffer that contains /// audio_block_t * pointers. /// @returns pointer to the underlying RingBuffer @@ -177,6 +191,7 @@ private: MemType m_type; ///< when 0, INTERNAL memory, when 1, external MEMORY. RingBuffer *m_ringBuffer = nullptr; ///< When using INTERNAL memory, a RingBuffer will be created. ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided. + size_t m_maxDelaySamples = 0; ///< stores the number of audio samples in the AudioDelay. }; /**************************************************************************//** @@ -246,7 +261,7 @@ public: /// Process the data using the configured IIR filter /// @details output and input can be the same pointer if in-place modification is desired - /// @param output pointer to where the output results will be written + /// @param output poinvoid combine(audio_block_t *out, audio_block_t *in0, audio_block_t *in1)ter to where the output results will be written /// @param input pointer to where the input data will be read from /// @param numSampmles number of samples to process bool process(int16_t *output, int16_t *input, size_t numSamples); @@ -297,7 +312,91 @@ private: }; -} +} // namespace BALibrary + +namespace BALibrary { + +/**************************************************************************//** + * The class will automate a parameter using a trigger from a start value to an + * end value, using either a preprogrammed function or a user-provided LUT. + *****************************************************************************/ +template +class ParameterAutomation +{ +public: + enum class Function : unsigned { + NOT_CONFIGURED = 0, ///< Initial, unconfigured stage + HOLD, ///< f(x) = constant + LINEAR, ///< f(x) = x + EXPONENTIAL, ///< f(x) = exp(-k*x) + LOGARITHMIC, ///< f(x) = + PARABOLIC, ///< f(x) = x^2 + LOOKUP_TABLE ///< f(x) = lut(x) + }; + ParameterAutomation(); + ParameterAutomation(T startValue, T endValue, size_t durationSamples, Function function = Function::LINEAR); + ParameterAutomation(T startValue, T endValue, float durationMilliseconds, Function function = Function::LINEAR); + virtual ~ParameterAutomation(); + + /// set the start and end values for the automation + /// @param function select which automation curve (function) to use + /// @param startValue after reset, parameter automation start from this value + /// @param endValue after the automation duration, paramter will finish at this value + /// @param durationSamples number of samples to transition from startValue to endValue + void reconfigure(T startValue, T endValue, size_t durationSamples, Function function = Function::LINEAR); + void reconfigure(T startValue, T endValue, float durationMilliseconds, Function function = Function::LINEAR); + + /// Start the automation from startValue + void trigger(); + + /// Retrieve the next calculated automation value + /// @returns the calculated parameter value of templated type T + T getNextValue(); + + bool isFinished() { return !m_running; } + +private: + Function m_function; + T m_startValue; + T m_endValue; + bool m_running = false; + float m_currentValueX; ///< the current value of x in f(x) + size_t m_duration; + //float m_coeffs[3]; ///< some general coefficient storage + float m_slopeX; + float m_scaleY; + bool m_positiveSlope = true; +}; + + +// TODO: initialize with const number of sequences with null type that automatically skips +// then register each new sequence. +constexpr int MAX_PARAMETER_SEQUENCES = 32; +template +class ParameterAutomationSequence +{ +public: + ParameterAutomationSequence() = delete; + ParameterAutomationSequence(int numStages); + virtual ~ParameterAutomationSequence(); + + void setupParameter(int index, T startValue, T endValue, size_t durationSamples, typename ParameterAutomation::Function function); + void setupParameter(int index, T startValue, T endValue, float durationMilliseconds, typename ParameterAutomation::Function function); + + /// Trigger a the automation sequence until numStages is reached or a Function is ParameterAutomation::Function::NOT_CONFIGURED + void trigger(); + + T getNextValue(); + bool isFinished(); + +private: + ParameterAutomation *m_paramArray[MAX_PARAMETER_SEQUENCES]; + int m_currentIndex = 0; + int m_numStages = 0; + bool m_running = false; +}; + +} // BALibrary -#endif /* __BAGUITAR_LIBBASICFUNCTIONS_H */ +#endif /* __BALIBRARY_LIBBASICFUNCTIONS_H */ diff --git a/src/LibMemoryManagement.h b/src/LibMemoryManagement.h index bb450d1..288e581 100644 --- a/src/LibMemoryManagement.h +++ b/src/LibMemoryManagement.h @@ -26,15 +26,15 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef __BAGUITAR_LIBMEMORYMANAGEMENT_H -#define __BAGUITAR_LIBMEMORYMANAGEMENT_H +#ifndef __BALIBRARY_LIBMEMORYMANAGEMENT_H +#define __BALIBRARY_LIBMEMORYMANAGEMENT_H #include #include "BAHardware.h" #include "BASpiMemory.h" -namespace BAGuitar { +namespace BALibrary { /**************************************************************************//** * MemConfig contains the configuration information associated with a particular @@ -182,7 +182,7 @@ public: /// @details note that currently, memory cannot be allocated. /// @param mem specifies which memory to query, default is memory 0 /// @returns the available memory in bytes - size_t availableMemory(BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0); + size_t availableMemory(BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0); /// Request memory be allocated for the provided slot /// @param slot a pointer to the global slot object to which memory will be allocated @@ -190,7 +190,7 @@ public: /// @param mem specify which external memory to allocate from /// @param useDma when true, DMA is used for SPI port, else transfers block until complete /// @returns true on success, otherwise false on error - bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = false); + bool requestMemory(ExtMemSlot *slot, float delayMilliseconds, BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0, bool useDma = false); /// Request memory be allocated for the provided slot /// @param slot a pointer to the global slot object to which memory will be allocated @@ -198,15 +198,15 @@ public: /// @param mem specify which external memory to allocate from /// @param useDma when true, DMA is used for SPI port, else transfers block until complete /// @returns true on success, otherwise false on error - bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem = BAGuitar::MemSelect::MEM0, bool useDma = false); + bool requestMemory(ExtMemSlot *slot, size_t sizeBytes, BALibrary::MemSelect mem = BALibrary::MemSelect::MEM0, bool useDma = false); private: static bool m_configured; ///< there should only be one instance of ExternalSramManager in the whole project - static MemConfig m_memConfig[BAGuitar::NUM_MEM_SLOTS]; ///< store the configuration information for each external memory + static MemConfig m_memConfig[BALibrary::NUM_MEM_SLOTS]; ///< store the configuration information for each external memory }; -} +} // BALibrary -#endif /* __LIBMEMORYMANAGEMENT_H */ +#endif /* __BALIBRARY_LIBMEMORYMANAGEMENT_H */ diff --git a/src/common/AudioDelay.cpp b/src/common/AudioDelay.cpp index 704233c..9fec4ea 100644 --- a/src/common/AudioDelay.cpp +++ b/src/common/AudioDelay.cpp @@ -21,7 +21,7 @@ #include "Audio.h" #include "LibBasicFunctions.h" -namespace BAGuitar { +namespace BALibrary { //////////////////////////////////////////////////// // AudioDelay @@ -34,6 +34,7 @@ AudioDelay::AudioDelay(size_t maxSamples) // INTERNAL memory consisting of audio_block_t data structures. QueuePosition pos = calcQueuePosition(maxSamples); m_ringBuffer = new RingBuffer(pos.index+2); // If the delay is in queue x, we need to overflow into x+1, thus x+2 total buffers. + m_maxDelaySamples = maxSamples; } AudioDelay::AudioDelay(float maxDelayTimeMs) @@ -46,6 +47,7 @@ AudioDelay::AudioDelay(ExtMemSlot *slot) { m_type = (MemType::MEM_EXTERNAL); m_slot = slot; + m_maxDelaySamples = (slot->size() / sizeof(int16_t)) - AUDIO_BLOCK_SAMPLES; } AudioDelay::~AudioDelay() @@ -93,6 +95,15 @@ audio_block_t* AudioDelay::getBlock(size_t index) return ret; } +size_t AudioDelay::getMaxDelaySamples() +{ + if (m_type == MemType::MEM_EXTERNAL) { + // update the max delay sample size + m_maxDelaySamples = (m_slot->size() / sizeof(int16_t)) - AUDIO_BLOCK_SAMPLES; + } + return m_maxDelaySamples; +} + bool AudioDelay::getSamples(audio_block_t *dest, size_t offsetSamples, size_t numSamples) { if (!dest) { @@ -159,8 +170,9 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offsetSamples, size_t nu return true; } else { - // numSampmles is > than total slot size + // numSamples is > than total slot size Serial.println("getSamples(): ERROR numSamples > total slot size"); + Serial.println(numSamples + String(" > ") + m_slot->size()); return false; } } diff --git a/src/common/AudioHelpers.cpp b/src/common/AudioHelpers.cpp index ae7cfcf..49e2a31 100644 --- a/src/common/AudioHelpers.cpp +++ b/src/common/AudioHelpers.cpp @@ -21,7 +21,7 @@ #include "Audio.h" #include "LibBasicFunctions.h" -namespace BAGuitar { +namespace BALibrary { size_t calcAudioSamples(float milliseconds) { @@ -69,6 +69,11 @@ void gainAdjust(audio_block_t *out, audio_block_t *in, float vol, int coeffShift arm_scale_q15(in->data, scale, coeffShift, out->data, AUDIO_BLOCK_SAMPLES); } +void combine(audio_block_t *out, audio_block_t *in0, audio_block_t *in1) +{ + arm_add_q15 (in0->data, in1->data, out->data, AUDIO_BLOCK_SAMPLES); +} + void clearAudioBlock(audio_block_t *block) { memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES); diff --git a/src/common/ExtMemSlot.cpp b/src/common/ExtMemSlot.cpp index 57385d7..c857cbe 100644 --- a/src/common/ExtMemSlot.cpp +++ b/src/common/ExtMemSlot.cpp @@ -23,7 +23,7 @@ #include "Audio.h" #include "LibMemoryManagement.h" -namespace BAGuitar { +namespace BALibrary { ///////////////////////////////////////////////////////////////////////////// // MEM SLOT diff --git a/src/common/ExternalSramManager.cpp b/src/common/ExternalSramManager.cpp index cc0f7f9..7083e0c 100644 --- a/src/common/ExternalSramManager.cpp +++ b/src/common/ExternalSramManager.cpp @@ -23,13 +23,13 @@ #include "Audio.h" #include "LibMemoryManagement.h" -namespace BAGuitar { +namespace BALibrary { ///////////////////////////////////////////////////////////////////////////// // EXTERNAL SRAM MANAGER ///////////////////////////////////////////////////////////////////////////// bool ExternalSramManager::m_configured = false; -MemConfig ExternalSramManager::m_memConfig[BAGuitar::NUM_MEM_SLOTS]; +MemConfig ExternalSramManager::m_memConfig[BALibrary::NUM_MEM_SLOTS]; ExternalSramManager::ExternalSramManager(unsigned numMemories) @@ -37,8 +37,8 @@ ExternalSramManager::ExternalSramManager(unsigned numMemories) // Initialize the static memory configuration structs if (!m_configured) { for (unsigned i=0; i < NUM_MEM_SLOTS; i++) { - m_memConfig[i].size = MEM_MAX_ADDR[i]; - m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i]; + m_memConfig[i].size = MEM_MAX_ADDR[i]+1; + m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i]+1; m_memConfig[i].nextAvailable = 0; m_memConfig[i].m_spi = nullptr; @@ -60,19 +60,19 @@ ExternalSramManager::~ExternalSramManager() } } -size_t ExternalSramManager::availableMemory(BAGuitar::MemSelect mem) +size_t ExternalSramManager::availableMemory(BALibrary::MemSelect mem) { return m_memConfig[mem].totalAvailable; } -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BAGuitar::MemSelect mem, bool useDma) +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, float delayMilliseconds, BALibrary::MemSelect mem, bool useDma) { // convert the time to numer of samples size_t delayLengthInt = (size_t)((delayMilliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); return requestMemory(slot, delayLengthInt * sizeof(int16_t), mem, useDma); } -bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGuitar::MemSelect mem, bool useDma) +bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BALibrary::MemSelect mem, bool useDma) { if (m_memConfig[mem].totalAvailable >= sizeBytes) { @@ -86,10 +86,10 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu if (!m_memConfig[mem].m_spi) { if (useDma) { - m_memConfig[mem].m_spi = new BAGuitar::BASpiMemoryDMA(static_cast(mem)); + m_memConfig[mem].m_spi = new BALibrary::BASpiMemoryDMA(static_cast(mem)); slot->m_useDma = true; } else { - m_memConfig[mem].m_spi = new BAGuitar::BASpiMemory(static_cast(mem)); + m_memConfig[mem].m_spi = new BALibrary::BASpiMemory(static_cast(mem)); slot->m_useDma = false; } if (!m_memConfig[mem].m_spi) { @@ -111,7 +111,9 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu return true; } else { // there is not enough memory available for the request - + Serial.println(String("ExternalSramManager::requestMemory(): Insufficient memory in slot, request/available: ") + + sizeBytes + String(" : ") + + m_memConfig[mem].totalAvailable); return false; } } diff --git a/src/common/IirBiquadFilter.cpp b/src/common/IirBiquadFilter.cpp index f2ae1c7..82eec80 100644 --- a/src/common/IirBiquadFilter.cpp +++ b/src/common/IirBiquadFilter.cpp @@ -21,7 +21,7 @@ #include "Audio.h" #include "LibBasicFunctions.h" -namespace BAGuitar { +namespace BALibrary { //////////////////////////////////////////////////// // IirBiQuadFilter diff --git a/src/common/ParameterAutomation.cpp b/src/common/ParameterAutomation.cpp new file mode 100644 index 0000000..bc3e4d2 --- /dev/null +++ b/src/common/ParameterAutomation.cpp @@ -0,0 +1,241 @@ +/* + * ParameterAutomation.cpp + * + * Created on: April 14, 2018 + * Author: slascos + * + * 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 . +*/ + +#include "LibBasicFunctions.h" + +namespace BALibrary { + +/////////////////////////////////////////////////////////////////////////////// +// ParameterAutomation +/////////////////////////////////////////////////////////////////////////////// +constexpr int LINEAR_SLOPE = 0; +constexpr float EXPONENTIAL_K = 5.0f; +constexpr float EXP_EXPONENTIAL_K = expf(EXPONENTIAL_K); + +template +ParameterAutomation::ParameterAutomation() +{ + reconfigure(0.0f, 0.0f, static_cast(0), Function::NOT_CONFIGURED); +} + +template +ParameterAutomation::ParameterAutomation(T startValue, T endValue, float durationMilliseconds, Function function) +{ + reconfigure(startValue, endValue, calcAudioSamples(durationMilliseconds), function); +} + +template +ParameterAutomation::ParameterAutomation(T startValue, T endValue, size_t durationSamples, Function function) +{ + reconfigure(startValue, endValue, durationSamples, function); +} + +template +ParameterAutomation::~ParameterAutomation() +{ + +} + +template +void ParameterAutomation::reconfigure(T startValue, T endValue, float durationMilliseconds, Function function) +{ + reconfigure(startValue, endValue, calcAudioSamples(durationMilliseconds), function); +} + +template +void ParameterAutomation::reconfigure(T startValue, T endValue, size_t durationSamples, Function function) +{ + m_function = function; + m_startValue = startValue; + m_endValue = endValue; + m_currentValueX = 0.0f; + m_duration = durationSamples; + m_running = false; + + float duration = m_duration / static_cast(AUDIO_BLOCK_SAMPLES); + m_slopeX = (1.0f / static_cast(duration)); + m_scaleY = abs(endValue - startValue); + if (endValue >= startValue) { + // value is increasing + m_positiveSlope = true; + } else { + // value is decreasing + m_positiveSlope = false; + } +} + + +template +void ParameterAutomation::trigger() +{ + m_currentValueX = 0.0f; + m_running = true; +} + +template +T ParameterAutomation::getNextValue() +{ + if (m_running == false) { + return m_startValue; + } + + m_currentValueX += m_slopeX; + float value; + float returnValue; + + switch(m_function) { + case Function::EXPONENTIAL : + + if (m_positiveSlope) { + // Growth: f(x) = exp(k*x) / exp(k) + value = expf(EXPONENTIAL_K*m_currentValueX) / EXP_EXPONENTIAL_K; + } else { + // Decay: f(x) = 1 - exp(-k*x) + value = 1.0f - expf(-EXPONENTIAL_K*m_currentValueX); + } + break; + case Function::PARABOLIC : + value = m_currentValueX*m_currentValueX; + break; + case Function::LOOKUP_TABLE : + case Function::LINEAR : + default : + value = m_currentValueX; + break; + } + + // Check if the automation is finished. + if (m_currentValueX >= 1.0f) { + m_currentValueX = 0.0f; + m_running = false; + return m_endValue; + } + + + if (m_positiveSlope) { + returnValue = m_startValue + (m_scaleY*value); + } else { + returnValue = m_startValue - (m_scaleY*value); + } +// Serial.println(String("Start/End values: ") + m_startValue + String(":") + m_endValue); + //Serial.print("Parameter m_currentValueX is "); Serial.println(m_currentValueX, 6); + //Serial.print("Parameter returnValue is "); Serial.println(returnValue, 6); + return returnValue; +} + +// Template instantiation +template class ParameterAutomation; +template class ParameterAutomation; +template class ParameterAutomation; + +/////////////////////////////////////////////////////////////////////////////// +// ParameterAutomationSequence +/////////////////////////////////////////////////////////////////////////////// +template +ParameterAutomationSequence::ParameterAutomationSequence(int numStages) +{ + if (numStages < MAX_PARAMETER_SEQUENCES) { + for (int i=0; i(); + } + for (int i=numStages; i +ParameterAutomationSequence::~ParameterAutomationSequence() +{ + for (int i=0; i +void ParameterAutomationSequence::setupParameter(int index, T startValue, T endValue, size_t durationSamples, typename ParameterAutomation::Function function) +{ + Serial.println(String("setupParameter() called with samples: ") + durationSamples); + m_paramArray[index]->reconfigure(startValue, endValue, durationSamples, function); + m_currentIndex = 0; +} + +template +void ParameterAutomationSequence::setupParameter(int index, T startValue, T endValue, float durationMilliseconds, typename ParameterAutomation::Function function) +{ + Serial.print(String("setupParameter() called with time: ")); Serial.println(durationMilliseconds, 6); + m_paramArray[index]->reconfigure(startValue, endValue, durationMilliseconds, function); + m_currentIndex = 0; +} + +template +void ParameterAutomationSequence::trigger(void) +{ + m_currentIndex = 0; + m_paramArray[0]->trigger(); + m_running = true; + //Serial.println("ParameterAutomationSequence::trigger() called"); +} + +template +T ParameterAutomationSequence::getNextValue() +{ + // Get the next value + T nextValue = m_paramArray[m_currentIndex]->getNextValue(); + + if (m_running) { + //Serial.println(String("ParameterAutomationSequence::getNextValue() is ") + nextValue + // + String(" from stage ") + m_currentIndex); + + // If current stage is done, trigger the next + if (m_paramArray[m_currentIndex]->isFinished()) { + Serial.println(String("Finished stage ") + m_currentIndex); + m_currentIndex++; + + if (m_currentIndex >= m_numStages) { + // Last stage already finished + Serial.println("Last stage finished"); + m_running = false; + m_currentIndex = 0; + } else { + // trigger the next stage + m_paramArray[m_currentIndex]->trigger(); + } + } + } + + return nextValue; +} + +template +bool ParameterAutomationSequence::isFinished() +{ + return !m_running; +} + +// Template instantiation +template class ParameterAutomationSequence; +template class ParameterAutomationSequence; +template class ParameterAutomationSequence; + +} diff --git a/src/effects/AudioEffectAnalogDelay.cpp b/src/effects/AudioEffectAnalogDelay.cpp index 6cf1ead..52a0438 100644 --- a/src/effects/AudioEffectAnalogDelay.cpp +++ b/src/effects/AudioEffectAnalogDelay.cpp @@ -8,7 +8,9 @@ #include "AudioEffectAnalogDelayFilters.h" #include "AudioEffectAnalogDelay.h" -namespace BAGuitar { +using namespace BALibrary; + +namespace BAEffects { constexpr int MIDI_CHANNEL = 0; constexpr int MIDI_CONTROL = 1; @@ -166,6 +168,11 @@ void AudioEffectAnalogDelay::delay(float milliseconds) { size_t delaySamples = calcAudioSamples(milliseconds); + if (delaySamples > m_memory->getMaxDelaySamples()) { + // this exceeds max delay value, limit it. + delaySamples = m_memory->getMaxDelaySamples(); + } + if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } if (!m_externalMemory) { diff --git a/src/effects/AudioEffectAnalogDelayFilters.h b/src/effects/AudioEffectAnalogDelayFilters.h index 71f70bd..473f9cd 100644 --- a/src/effects/AudioEffectAnalogDelayFilters.h +++ b/src/effects/AudioEffectAnalogDelayFilters.h @@ -21,7 +21,7 @@ *****************************************************************************/ #include -namespace BAGuitar { +namespace BAEffects { // The number of stages in the analog-response Biquad filter constexpr unsigned MAX_NUM_FILTER_STAGES = 4; diff --git a/src/effects/AudioEffectSOS.cpp b/src/effects/AudioEffectSOS.cpp new file mode 100644 index 0000000..2512261 --- /dev/null +++ b/src/effects/AudioEffectSOS.cpp @@ -0,0 +1,304 @@ +/* + * AudioEffectSOS.cpp + * + * Created on: Apr 14, 2018 + * Author: blackaddr + */ + +#include "AudioEffectSOS.h" +#include "LibBasicFunctions.h" + +using namespace BALibrary; + +namespace BAEffects { + +constexpr int MIDI_CHANNEL = 0; +constexpr int MIDI_CONTROL = 1; + +constexpr float MAX_GATE_OPEN_TIME_MS = 3000.0f; +constexpr float MAX_GATE_CLOSE_TIME_MS = 1000.0f; + +constexpr int GATE_OPEN_STAGE = 0; +constexpr int GATE_HOLD_STAGE = 1; +constexpr int GATE_CLOSE_STAGE = 2; + +AudioEffectSOS::AudioEffectSOS(float maxDelayMs) +: AudioStream(1, m_inputQueueArray) +{ + m_memory = new AudioDelay(maxDelayMs); + m_maxDelaySamples = calcAudioSamples(maxDelayMs); + m_externalMemory = false; +} + +AudioEffectSOS::AudioEffectSOS(size_t numSamples) +: AudioStream(1, m_inputQueueArray) +{ + m_memory = new AudioDelay(numSamples); + m_maxDelaySamples = numSamples; + m_externalMemory = false; +} + +AudioEffectSOS::AudioEffectSOS(ExtMemSlot *slot) +: AudioStream(1, m_inputQueueArray) +{ + m_memory = new AudioDelay(slot); + m_externalMemory = true; +} + +AudioEffectSOS::~AudioEffectSOS() +{ + if (m_memory) delete m_memory; +} + +void AudioEffectSOS::setGateLedGpio(int pinId) +{ + m_gateLedPinId = pinId; + pinMode(static_cast(m_gateLedPinId), OUTPUT); +} + +void AudioEffectSOS::enable(void) +{ + m_enable = true; + if (m_externalMemory) { + // Because we hold the previous output buffer for an update cycle, the maximum delay is actually + // 1 audio block mess then the max delay returnable from the memory. + m_maxDelaySamples = m_memory->getMaxDelaySamples(); + Serial.println(String("SOS Enabled with delay length ") + m_maxDelaySamples + String(" samples")); + } + m_delaySamples = m_maxDelaySamples; + m_inputGateAuto.setupParameter(GATE_OPEN_STAGE, 0.0f, 1.0f, 1000.0f, ParameterAutomation::Function::EXPONENTIAL); + m_inputGateAuto.setupParameter(GATE_HOLD_STAGE, 1.0f, 1.0f, m_maxDelaySamples, ParameterAutomation::Function::HOLD); + m_inputGateAuto.setupParameter(GATE_CLOSE_STAGE, 1.0f, 0.0f, 1000.0f, ParameterAutomation::Function::EXPONENTIAL); + + m_clearFeedbackAuto.setupParameter(GATE_OPEN_STAGE, 1.0f, 0.0f, 1000.0f, ParameterAutomation::Function::EXPONENTIAL); + m_clearFeedbackAuto.setupParameter(GATE_HOLD_STAGE, 0.0f, 0.0f, m_maxDelaySamples, ParameterAutomation::Function::HOLD); + m_clearFeedbackAuto.setupParameter(GATE_CLOSE_STAGE, 0.0f, 1.0f, 1000.0f, ParameterAutomation::Function::EXPONENTIAL); +} + +void AudioEffectSOS::update(void) +{ + audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples + + // Check is block is disabled + if (m_enable == false) { + // do not transmit or process any audio, return as quickly as possible. + if (inputAudioBlock) release(inputAudioBlock); + + // release all held memory resources + if (m_previousBlock) { + release(m_previousBlock); m_previousBlock = nullptr; + } + if (!m_externalMemory) { + // when using internal memory we have to release all references in the ring buffer + while (m_memory->getRingBuffer()->size() > 0) { + audio_block_t *releaseBlock = m_memory->getRingBuffer()->front(); + m_memory->getRingBuffer()->pop_front(); + if (releaseBlock) release(releaseBlock); + } + } + return; + } + + // Check is block is bypassed, if so either transmit input directly or create silence + if ( (m_bypass == true) || (!inputAudioBlock) ) { + // transmit the input directly + if (!inputAudioBlock) { + // create silence + inputAudioBlock = allocate(); + if (!inputAudioBlock) { return; } // failed to allocate + else { + clearAudioBlock(inputAudioBlock); + } + } + transmit(inputAudioBlock, 0); + release(inputAudioBlock); + return; + } + + if (!inputAudioBlock) return; + + // Otherwise perform normal processing + // In order to make use of the SPI DMA, we need to request the read from memory first, + // then do other processing while it fills in the back. + audio_block_t *blockToOutput = nullptr; // this will hold the output audio + blockToOutput = allocate(); + if (!blockToOutput) return; // skip this update cycle due to failure + + // get the data. If using external memory with DMA, this won't be filled until + // later. + m_memory->getSamples(blockToOutput, m_delaySamples); + //Serial.println(String("Delay samples:") + m_delaySamples); + //Serial.println(String("Use dma: ") + m_memory->getSlot()->isUseDma()); + + // If using DMA, we need something else to do while that read executes, so + // move on to input preprocessing + + // Preprocessing + audio_block_t *preProcessed = allocate(); + // mix the input with the feedback path in the pre-processing stage + m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); + + // consider doing the BBD post processing here to use up more time while waiting + // for the read data to come back + audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); + //audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock); + //Serial.println("Done adding new block"); + + + // BACK TO OUTPUT PROCESSING + // Check if external DMA, if so, we need to be sure the read is completed + if (m_externalMemory && m_memory->getSlot()->isUseDma()) { + // Using DMA + while (m_memory->getSlot()->isReadBusy()) {} + } + + // perform the wet/dry mix mix + m_postProcessing(blockToOutput, blockToOutput); + transmit(blockToOutput); + + release(inputAudioBlock); + + if (m_previousBlock) + release(m_previousBlock); + m_previousBlock = blockToOutput; + + if (m_blockToRelease == m_previousBlock) { + Serial.println("ERROR: POINTER COLLISION"); + } + + if (m_blockToRelease) release(m_blockToRelease); + m_blockToRelease = blockToRelease; +} + + +void AudioEffectSOS::gateOpenTime(float milliseconds) +{ + // TODO - change the paramter automation to an automation sequence + m_openTimeMs = milliseconds; + m_inputGateAuto.setupParameter(GATE_OPEN_STAGE, 0.0f, 1.0f, m_openTimeMs, ParameterAutomation::Function::EXPONENTIAL); + //m_clearFeedbackAuto.setupParameter(GATE_OPEN_STAGE, 1.0f, 0.0f, m_openTimeMs, ParameterAutomation::Function::EXPONENTIAL); +} + +void AudioEffectSOS::gateCloseTime(float milliseconds) +{ + m_closeTimeMs = milliseconds; + m_inputGateAuto.setupParameter(GATE_CLOSE_STAGE, 1.0f, 0.0f, m_closeTimeMs, ParameterAutomation::Function::EXPONENTIAL); + //m_clearFeedbackAuto.setupParameter(GATE_CLOSE_STAGE, 0.0f, 1.0f, m_closeTimeMs, ParameterAutomation::Function::EXPONENTIAL); +} + +//////////////////////////////////////////////////////////////////////// +// MIDI PROCESSING +//////////////////////////////////////////////////////////////////////// +void AudioEffectSOS::processMidi(int channel, int control, int value) +{ + + float val = (float)value / 127.0f; + + if ((m_midiConfig[GATE_OPEN_TIME][MIDI_CHANNEL] == channel) && + (m_midiConfig[GATE_OPEN_TIME][MIDI_CONTROL] == control)) { + // Gate Open Time + gateOpenTime(val * MAX_GATE_OPEN_TIME_MS); + Serial.println(String("AudioEffectSOS::gate open time (ms): ") + m_openTimeMs); + return; + } + + if ((m_midiConfig[GATE_CLOSE_TIME][MIDI_CHANNEL] == channel) && + (m_midiConfig[GATE_CLOSE_TIME][MIDI_CONTROL] == control)) { + // Gate Close Time + gateCloseTime(val * MAX_GATE_CLOSE_TIME_MS); + Serial.println(String("AudioEffectSOS::gate close time (ms): ") + m_closeTimeMs); + return; + } + + if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) && + (m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { + // Feedback + Serial.println(String("AudioEffectSOS::feedback: ") + 100*val + String("%")); + feedback(val); + return; + } + + if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && + (m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { + // Volume + Serial.println(String("AudioEffectSOS::volume: ") + 100*val + String("%")); + volume(val); + return; + } + + if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && + (m_midiConfig[BYPASS][MIDI_CONTROL] == control)) { + // Bypass + if (value >= 65) { bypass(false); Serial.println(String("AudioEffectSOS::not bypassed -> ON") + value); } + else { bypass(true); Serial.println(String("AudioEffectSOS::bypassed -> OFF") + value); } + return; + } + + if ((m_midiConfig[GATE_TRIGGER][MIDI_CHANNEL] == channel) && + (m_midiConfig[GATE_TRIGGER][MIDI_CONTROL] == control)) { + // The gate is triggered by any value + Serial.println(String("AudioEffectSOS::Gate Triggered!")); + m_inputGateAuto.trigger(); + return; + } + + if ((m_midiConfig[CLEAR_FEEDBACK_TRIGGER][MIDI_CHANNEL] == channel) && + (m_midiConfig[CLEAR_FEEDBACK_TRIGGER][MIDI_CONTROL] == control)) { + // The gate is triggered by any value + Serial.println(String("AudioEffectSOS::Clear feedback Triggered!")); + m_clearFeedbackAuto.trigger(); + return; + } +} + +void AudioEffectSOS::mapMidiControl(int parameter, int midiCC, int midiChannel) +{ + if (parameter >= NUM_CONTROLS) { + return ; // Invalid midi parameter + } + m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; + m_midiConfig[parameter][MIDI_CONTROL] = midiCC; +} + +////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS +////////////////////////////////////////////////////////////////////// +void AudioEffectSOS::m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal) +{ + if ( out && input && delayedSignal) { + // Multiply the input signal by the automated gate value + // Multiply the delayed signal by the user set feedback value + // Then combine the two + + float gateVol = m_inputGateAuto.getNextValue(); + float feedbackAdjust = m_clearFeedbackAuto.getNextValue(); + audio_block_t tempAudioBuffer; + + gainAdjust(out, input, gateVol, 0); // last paremeter is coeff shift, 0 bits + gainAdjust(&tempAudioBuffer, delayedSignal, m_feedback*feedbackAdjust, 0); // last parameter is coeff shift, 0 bits + combine(out, out, &tempAudioBuffer); + + } else if (input) { + memcpy(out->data, input->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + } + + // Update the gate LED + if (m_gateLedPinId >= 0) { + if (m_inputGateAuto.isFinished() && m_clearFeedbackAuto.isFinished()) { + digitalWriteFast(m_gateLedPinId, 0x0); + } else { + digitalWriteFast(m_gateLedPinId, 0x1); + } + } +} + +void AudioEffectSOS::m_postProcessing(audio_block_t *out, audio_block_t *in) +{ + gainAdjust(out, out, m_volume, 0); +} + + +} // namespace BAEffects + + + diff --git a/src/effects/BAAudioEffectDelayExternal.cpp b/src/effects/BAAudioEffectDelayExternal.cpp index acdf3d2..1f1c215 100644 --- a/src/effects/BAAudioEffectDelayExternal.cpp +++ b/src/effects/BAAudioEffectDelayExternal.cpp @@ -20,7 +20,9 @@ #include "BAAudioEffectDelayExternal.h" -namespace BAGuitar { +using namespace BALibrary; + +namespace BAEffects { #define SPISETTING SPISettings(20000000, MSBFIRST, SPI_MODE0) @@ -49,7 +51,7 @@ BAAudioEffectDelayExternal::BAAudioEffectDelayExternal(MemSelect mem) initialize(mem); } -BAAudioEffectDelayExternal::BAAudioEffectDelayExternal(BAGuitar::MemSelect type, float delayLengthMs) +BAAudioEffectDelayExternal::BAAudioEffectDelayExternal(BALibrary::MemSelect type, float delayLengthMs) : AudioStream(1, m_inputQueueArray) { unsigned delayLengthInt = (delayLengthMs*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f; @@ -289,4 +291,4 @@ inline void BAAudioEffectDelayExternal::m_stopUsingSPI(int spiBus) { #endif -} /* namespace BAGuitar */ +} /* namespace BAEffects */ diff --git a/src/peripherals/BAAudioControlWM8731.cpp b/src/peripherals/BAAudioControlWM8731.cpp index f5b2ae4..e1960e3 100644 --- a/src/peripherals/BAAudioControlWM8731.cpp +++ b/src/peripherals/BAAudioControlWM8731.cpp @@ -21,7 +21,7 @@ #include #include "BAAudioControlWM8731.h" -namespace BAGuitar { +namespace BALibrary { // use const instead of define for proper scoping constexpr int WM8731_I2C_ADDR = 0x1A; @@ -341,4 +341,4 @@ bool BAAudioControlWM8731::write(unsigned int reg, unsigned int val) return true; } -} /* namespace BAGuitar */ +} /* namespace BALibrary */ diff --git a/src/peripherals/BAGpio.cpp b/src/peripherals/BAGpio.cpp index ccf09c4..e2d3c79 100644 --- a/src/peripherals/BAGpio.cpp +++ b/src/peripherals/BAGpio.cpp @@ -21,7 +21,7 @@ #include "Arduino.h" #include "BAGpio.h" -namespace BAGuitar { +namespace BALibrary { BAGpio::BAGpio() { @@ -85,4 +85,4 @@ int BAGpio::toggleLed() } -} /* namespace BAGuitar */ +} /* namespace BALibrary */ diff --git a/src/peripherals/BASpiMemory.cpp b/src/peripherals/BASpiMemory.cpp index 05d006d..d91d7a7 100644 --- a/src/peripherals/BASpiMemory.cpp +++ b/src/peripherals/BASpiMemory.cpp @@ -21,7 +21,7 @@ #include "Arduino.h" #include "BASpiMemory.h" -namespace BAGuitar { +namespace BALibrary { // MEM0 Settings constexpr int SPI_CS_MEM0 = 15; @@ -468,4 +468,4 @@ bool BASpiMemoryDMA::isReadBusy(void) const return (m_rxTransfer[0].busy() or m_rxTransfer[1].busy()); } -} /* namespace BAGuitar */ +} /* namespace BALibrary */