Skip to content

Commit

Permalink
Fix #21: apply map function before hysteresis
Browse files Browse the repository at this point in the history
  • Loading branch information
tttapa committed Aug 9, 2019
1 parent 6de1b77 commit 31edaa6
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* This is an example of the VolumeControl class of the Control Surface library.
*
* @boards Teensy 3.x
*
* Connections
* -----------
*
* - A0: wiper of a potentiometer to change the output volume
* - 9: BCK (I²S)
* - 11: SCK (I²S)
* - 22: DIN (I²S)
* - 23: LRCK (I²S)
*
* Select a USB audio option from the Tools menu in the IDE.
*
* Behavior
* --------
*
* Upload the sketch, and select the Control Surface as the audio output of your
* computer. Connect the output of the DAC to a pair of headphones or powered
* speakers, and play some music.
* You can now adjust the volume using the potentiometer.
*
* Mapping
* -------
*
* None.
*
* Written by
*
*/

#include <Control_Surface.h> // Include the Control Surface library

AudioInputUSB audioInputUSB; // The audio input from the USB connection
AudioMixer4 mixer_L; // Two mixers for volume control
AudioMixer4 mixer_R;
AudioOutputI2S audioOutputI2S; // The output to the I²S DAC

AudioConnection patchCord1(audioInputUSB, 0, mixer_L, 0); // Connect the input
AudioConnection patchCord3(audioInputUSB, 1, mixer_R, 0); // to the mixer
AudioConnection patchCord5(mixer_L, 0, audioOutputI2S, 1); // Connect the mixer
AudioConnection patchCord6(mixer_R, 0, audioOutputI2S, 0); // to the output

// Instantiate a VolumeControl object with a potentiometer on pin A0 and a
// maximum gain of 1.0
VolumeControl<2> volume = {{&mixer_L, &mixer_R}, A0, 1.0};

void setup() {
volume.begin();
// Add a dead zone if you can't get the volume all the way to zero
// volume.map([](analog_t i) -> analog_t {
// return map(constrain(i, 10, 1013), 10, 1013, 0, 1023);
// });
AudioMemory(6);
}

void loop() {
volume.update();
}
38 changes: 38 additions & 0 deletions examples/examples.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,44 @@
* https://github.com/tttapa/Control-Surface
*/

/**
* @example "1.Volume-Controlled-USB-DAC.ino"
*
* 1.Volume-Controlled-USB-DAC
* ===========================
*
* This is an example of the VolumeControl class of the Control Surface library.
*
* @boards Teensy 3.x
*
* Connections
* -----------
*
* - A0: wiper of a potentiometer to change the output volume
* - 9: BCK (I²S)
* - 11: SCK (I²S)
* - 22: DIN (I²S)
* - 23: LRCK (I²S)
*
* Select a USB audio option from the Tools menu in the IDE.
*
* Behavior
* --------
*
* Upload the sketch, and select the Control Surface as the audio output of your
* computer. Connect the output of the DAC to a pair of headphones or powered
* speakers, and play some music.
* You can now adjust the volume using the potentiometer.
*
* Mapping
* -------
*
* None.
*
* Written by
*
*/

/**
* @example "VU-Meter-Bridge.ino"
*
Expand Down
16 changes: 15 additions & 1 deletion src/Audio/VolumeControl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class VolumeControl : public Updatable<Potentiometer> {
* object.
* @param analogPin
* The analog pin with the potentiometer connected.
* @param The maximum gain for the mixers.
* @param maxGain
* The maximum gain for the mixers.
*/
VolumeControl(const Array<AudioMixer4 *, N> &mixers, pin_t analogPin,
float maxGain = 1.0)
Expand All @@ -52,6 +53,19 @@ class VolumeControl : public Updatable<Potentiometer> {
*/
void begin() override {}

/**
* @brief Specify a mapping function that is applied to the raw
* analog value before setting the volume.
*
* @param fn
* A function pointer to the mapping function. This function
* should take the filtered analog value of 10 bits as a
* parameter, and should return a value of 10 bits.
*
* @see FilteredAnalog::map
*/
void map(MappingFunction fn) { filteredAnalog.map(fn); }

private:
Array<AudioMixer4 *, N> mixers;
FilteredAnalog<7> filteredAnalog;
Expand Down
2 changes: 1 addition & 1 deletion src/Def/Def.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ constexpr pin_t NO_PIN = 1 << (8 * sizeof(pin_t) - 1);

/// A function pointer to a mapping function to map analog values.
/// @see MIDIFilteredAnalog::map()
using MappingFunction = uint8_t (*)(uint8_t);
using MappingFunction = analog_t (*)(analog_t);

/// An easy alias for two-dimensional Arrays.
template <class T, size_t nb_rows, size_t nb_cols>
Expand Down
36 changes: 18 additions & 18 deletions src/Hardware/FilteredAnalog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ class FilteredAnalog {
*
* @param fn
* A function pointer to the mapping function. This function
* should take the filtered value (of `PRECISION` bits wide) as a
* parameter, and should return a value of `PRECISION` bits.
* should take the filtered value (of 10 bits wide) as a
* parameter, and should return a value of 10 bits.
*
* @note Applying the mapping function before filtering could result in
* the noise being amplified to such an extent that filtering it
* afterwards would be ineffective.
* Applying it after hysteresis would result in a lower resolution.
* That's why the mapping function is applied after filtering and
* hysteresis.
* before hysteresis.
*/
void map(MappingFunction fn);

Expand All @@ -67,13 +68,13 @@ class FilteredAnalog {
uint8_t getValue() const;

/**
* @brief Get the filtered value of the analog input without the mapping
* function applied.
* @brief Get the filtered value of the analog input any filtering or
* mapping applied.
*
* @return The filtered value of the analog input, as a number
* of `PRECISION` bits wide.
* @return The filtered value of the analog input, as a number of 10 bits
* wide.
*/
uint8_t getRawValue() const;
analog_t getRawValue() const;

private:
const pin_t analogPin;
Expand All @@ -96,23 +97,22 @@ FilteredAnalog<PRECISION>::FilteredAnalog(pin_t analogPin)

template <uint8_t PRECISION>
bool FilteredAnalog<PRECISION>::update() {
analog_t input =
ExtIO::analogRead(analogPin); // read the raw analog input value
input = filter.filter(input); // apply a low-pass EMA filter
return hysteresis.update(input); // apply hysteresis
analog_t input = getRawValue(); // read the raw analog input value
input = filter.filter(input); // apply a low-pass EMA filter
if (mapFn) // If a mapping function is specified,
input = mapFn(input); // apply it
return hysteresis.update(input); // apply hysteresis, and return true if
// the value changed since last time
}

template <uint8_t PRECISION>
uint8_t FilteredAnalog<PRECISION>::getValue() const {
uint8_t value = getRawValue();
if (mapFn != nullptr) // if a map function is specified
value = mapFn(value); // apply the map function to the value
return value;
return hysteresis.getValue();
}

template <uint8_t PRECISION>
uint8_t FilteredAnalog<PRECISION>::getRawValue() const {
return hysteresis.getValue();
analog_t FilteredAnalog<PRECISION>::getRawValue() const {
return ExtIO::analogRead(analogPin);
}

template <uint8_t PRECISION>
Expand Down
2 changes: 1 addition & 1 deletion src/Helpers/TeensyUSBTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#endif

#if defined(USB_MIDI_AUDIO_SERIAL) || defined(USB_MIDI16_AUDIO_SERIAL) || \
defined(USB_EVERYTHING)
defined(USB_AUDIO) || defined(USB_EVERYTHING)
#define TEENSY_AUDIOUSB_ENABLED
#endif

Expand Down
12 changes: 8 additions & 4 deletions src/MIDI_Outputs/Abstract/MIDIFilteredAnalog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ class MIDIFilteredAnalogAddressable : public MIDIOutputElement {
*
* @param fn
* A function pointer to the mapping function. This function
* should take the filtered analog value of `PRECISION` bits as a
* parameter, and should return a value of `PRECISION` bits.
* should take the filtered analog value of 10 bits as a
* parameter, and should return a value of 10 bits.
*
* @see FilteredAnalog::map
*/
void map(MappingFunction fn) { filteredAnalog.map(fn); }

Expand Down Expand Up @@ -107,8 +109,10 @@ class MIDIFilteredAnalog : public MIDIOutputElement {
*
* @param fn
* A function pointer to the mapping function. This function
* should take the filtered analog value of `PRECISION` bits as a
* parameter, and should return a value of `PRECISION` bits.
* should take the filtered analog value of 10 bits as a
* parameter, and should return a value of 10 bits.
*
* @see FilteredAnalog::map
*/
void map(MappingFunction fn) { filteredAnalog.map(fn); }

Expand Down
12 changes: 8 additions & 4 deletions src/MIDI_Outputs/Bankable/Abstract/MIDIFilteredAnalog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ class MIDIFilteredAnalogAddressable : public MIDIOutputElement,
*
* @param fn
* A function pointer to the mapping function. This function
* should take the filtered analog value of `PRECISION` bits as a
* parameter, and should return a value of `PRECISION` bits.
* should take the filtered analog value of 10 bits as a
* parameter, and should return a value of 10 bits.
*
* @see FilteredAnalog::map
*/
void map(MappingFunction fn) { filteredAnalog.map(fn); }

Expand Down Expand Up @@ -117,8 +119,10 @@ class MIDIFilteredAnalog : public MIDIOutputElement, public BankableMIDIOutput {
*
* @param fn
* A function pointer to the mapping function. This function
* should take the filtered analog value of `PRECISION` bits as a
* parameter, and should return a value of `PRECISION` bits.
* should take the filtered analog value of 10 bits as a
* parameter, and should return a value of 10 bits.
*
* @see FilteredAnalog::map
*/
void map(MappingFunction fn) { filteredAnalog.map(fn); }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ class MIDIFilteredAnalogAddressable : public MIDIOutputElement,
*
* @param fn
* A function pointer to the mapping function. This function
* should take the filtered analog value of `PRECISION` bits as a
* parameter, and should return a value of `PRECISION` bits.
* should take the filtered analog value of 10 bits as a
* parameter, and should return a value of 10 bits.
*
* @see FilteredAnalog::map
*/
void map(MappingFunction fn) { filteredAnalog.map(fn); }

Expand Down

0 comments on commit 31edaa6

Please sign in to comment.