commit b90fc26c1e6362d49a45a55a963a13e85041b545 Author: Blackaddr Date: Tue May 14 19:15:02 2024 -0400 Initial check in of pre-release version 0.1.0 diff --git a/BlackaddrAudio_Tuner.png b/BlackaddrAudio_Tuner.png new file mode 100644 index 0000000..5a6dc64 Binary files /dev/null and b/BlackaddrAudio_Tuner.png differ diff --git a/BlackaddrAudio_Tuner.prj b/BlackaddrAudio_Tuner.prj new file mode 100644 index 0000000..66c9a14 --- /dev/null +++ b/BlackaddrAudio_Tuner.prj @@ -0,0 +1,158 @@ +{ + "efxFileVersion": "1.4.0", + "company": "Blackaddr Audio", + "effectName": "Tuner", + "effectVersion": "0.1.0", + "coreVersion": "1.5.0", + "effectShortName": "NOISE", + "effectCategory": "Unspecified", + "effectDescription": "A noise gate wtih controllable threshold, opening and closing times and DC removal filter. Basic tuner included. ", + "numInputs": 1, + "numOutputs": 1, + "numControls": 5, + "effectFilename": "BlackaddrAudio_Tuner.efx", + "libraryName": "BlackaddrAudio_Tuner", + "cppClass": "BlackaddrAudio_Tuner::Tuner", + "cppInstBase": "_BlackaddrAudio_Tuner", + "constructorParams": "", + "isSingleton": false, + "controls": [ + { + "name": "Bypass", + "shortName": "BYP", + "description": "Bypasses the effect when enabled", + "config": [ + 0, + 0.0, + 1.0, + 0.0, + 1.0 + ], + "iconOn": "gfx/StompUpLedOffGold.png", + "iconOnHeight": 232, + "iconOff": "gfx/StompDownLedOnGold.png", + "iconOffHeight": 232, + "position": [ + 187, + 460 + ], + "scalingRatio": 0.75, + "userData": 1 + }, + { + "name": "Volume", + "shortName": "VOL", + "description": "Sets the effect output volume from -40dB to +10dB", + "config": [ + 3, + 0.0, + 10.0, + 8.0, + 0.1000000014901161 + ], + "iconPot": "gfx/Gold_Marshall_Knob_192x192.png", + "iconPotHeight": 192, + "supressValueLabel": 1, + "userData": 0 + }, + { + "name": "Tuner Silent", + "shortName": "T-SIL", + "description": "Mutes the output when tuner is enabled.", + "config": [ + 0, + 0.0, + 1.0, + 0.0, + 1.0 + ], + "iconOn": "gfx/SwitchSlideUpWhite.png", + "iconOnHeight": 128, + "iconOff": "gfx/SwitchSlideDownWhite.png", + "iconOffHeight": 128, + "position": [ + 300, + 450 + ], + "scalingRatio": 0.6000000238418579, + "userData": 0 + }, + { + "name": "Tuner Cents", + "shortName": "Cents", + "description": "Indicates the tuning pitch of the current note.", + "config": [ + 7, + -50.0, + 50.0, + 0.0, + 1.0 + ], + "iconPot": "gfx/tuner_cents_158h.png", + "iconPotHeight": 158, + "position": [ + 186, + 150 + ], + "scalingRatio": 0.800000011920929, + "supressValueLabel": 1, + "userData": 0 + }, + { + "name": "Tuner Note", + "shortName": "Note", + "description": "Indicates the note being tuned.", + "config": [ + 8, + 0.0, + 12.0, + 0.0, + 1.0 + ], + "iconEncoder": "gfx/tuner_notes_158h.png", + "iconEncoderHeight": 158, + "enums": [ + "C", + "C#", + "D", + "D#", + "E", + "F", + "F#", + "G", + "G#", + "A", + "A#", + "B", + "--" + ], + "position": [ + 186, + 150 + ], + "scalingRatio": 0.800000011920929, + "supressValueLabel": 1, + "userData": 0 + } + ], + "projectFileVersion": "1.0.0", + "companyLogo": "gfx/BlackaddrLogo.png", + "pedalIcon": "gfx/Tuner_icon.png", + "basePedal": "gfx/Tuner_base.png", + "publicHeaderFiles": [ + "inc/Tuner.h" + ], + "privateSourceFiles": [ + "src/TunerBase.cpp", + "src/Tuner.cpp" + ], + "modules": null, + "settings": { + "showModules": false, + "isSingleton": false, + "cppDefines": "", + "noWarnings": "", + "enableFastMath": false, + "enableO3": false + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c1cac0a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Blackaddr Audio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 0000000..da08136 --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1 @@ +*.efx diff --git a/gfx/BlackaddrLogo.png b/gfx/BlackaddrLogo.png new file mode 100644 index 0000000..27e6287 Binary files /dev/null and b/gfx/BlackaddrLogo.png differ diff --git a/gfx/Gold_Marshall_Knob_192x192.png b/gfx/Gold_Marshall_Knob_192x192.png new file mode 100644 index 0000000..4903437 Binary files /dev/null and b/gfx/Gold_Marshall_Knob_192x192.png differ diff --git a/gfx/StompDownLedOnGold.png b/gfx/StompDownLedOnGold.png new file mode 100644 index 0000000..4de1eea Binary files /dev/null and b/gfx/StompDownLedOnGold.png differ diff --git a/gfx/StompUpLedOffGold.png b/gfx/StompUpLedOffGold.png new file mode 100644 index 0000000..4272eee Binary files /dev/null and b/gfx/StompUpLedOffGold.png differ diff --git a/gfx/SwitchSlideDownWhite.png b/gfx/SwitchSlideDownWhite.png new file mode 100644 index 0000000..37a976c Binary files /dev/null and b/gfx/SwitchSlideDownWhite.png differ diff --git a/gfx/SwitchSlideUpWhite.png b/gfx/SwitchSlideUpWhite.png new file mode 100644 index 0000000..7ce5fb2 Binary files /dev/null and b/gfx/SwitchSlideUpWhite.png differ diff --git a/gfx/Tuner_base.png b/gfx/Tuner_base.png new file mode 100644 index 0000000..f5921ea Binary files /dev/null and b/gfx/Tuner_base.png differ diff --git a/gfx/Tuner_icon.png b/gfx/Tuner_icon.png new file mode 100644 index 0000000..6e7f733 Binary files /dev/null and b/gfx/Tuner_icon.png differ diff --git a/gfx/tuner_cents.png b/gfx/tuner_cents.png new file mode 100644 index 0000000..c32ba52 Binary files /dev/null and b/gfx/tuner_cents.png differ diff --git a/gfx/tuner_cents_158h.png b/gfx/tuner_cents_158h.png new file mode 100644 index 0000000..d2bb711 Binary files /dev/null and b/gfx/tuner_cents_158h.png differ diff --git a/gfx/tuner_notes_158h.png b/gfx/tuner_notes_158h.png new file mode 100644 index 0000000..5fd3b4d Binary files /dev/null and b/gfx/tuner_notes_158h.png differ diff --git a/gfx_design/TunerBase.xcf b/gfx_design/TunerBase.xcf new file mode 100644 index 0000000..8f0cb0c Binary files /dev/null and b/gfx_design/TunerBase.xcf differ diff --git a/gfx_design/TunerIcon.xcf b/gfx_design/TunerIcon.xcf new file mode 100644 index 0000000..36daf00 Binary files /dev/null and b/gfx_design/TunerIcon.xcf differ diff --git a/gfx_design/tuner_cents.knob b/gfx_design/tuner_cents.knob new file mode 100644 index 0000000..340eeeb Binary files /dev/null and b/gfx_design/tuner_cents.knob differ diff --git a/gfx_design/tuner_cents.xcf b/gfx_design/tuner_cents.xcf new file mode 100644 index 0000000..5ec1c44 Binary files /dev/null and b/gfx_design/tuner_cents.xcf differ diff --git a/gfx_design/tuner_notes.knob b/gfx_design/tuner_notes.knob new file mode 100644 index 0000000..16ef9d4 Binary files /dev/null and b/gfx_design/tuner_notes.knob differ diff --git a/inc/Tuner.h b/inc/Tuner.h new file mode 100644 index 0000000..dff7d1e --- /dev/null +++ b/inc/Tuner.h @@ -0,0 +1,124 @@ +/* + * Company: Blackaddr Audio + * Effect Name: Tuner + * Description: A noise gate wtih controllable threshold, opening and closing times and DC removal filter. Basic tuner included. + * + * This file was auto-generated by Aviate Audio Effect Creator for the Multiverse. + */ +#pragma once + +#include +#include +#include "Aviate/AudioEffectWrapper.h" + +//!s - START_USER_INCLUDES - put your #includes below this line before the matching END +//!e - END_USER_INCLUDES + +namespace BlackaddrAudio_Tuner { + +//!s - START_USER_EFFECT_TYPES - put your effect types below this line before the matching END +//!e - END_USER_EFFECT_TYPES + +class Tuner : public AudioStream, public Aviate::AudioEffectWrapper { +public: + static constexpr unsigned NUM_INPUTS = 1; + static constexpr unsigned NUM_OUTPUTS = 1; + + // List of effect control names + enum { + Bypass_e = 0, + Volume_e = 1, + TunerSilent_e = 2, + TunerCents_e = 3, + TunerNote_e = 4, + NUM_CONTROLS + }; + + //!s - START_USER_CLASS_TYPES - put your custom class types below this line before the matching END + //!e - END_USER_CLASS_TYPES + + Tuner(); + + //!s - START_USER_CONSTRUCTORS - put your custom constructors below this line before the matching END + //!e - END_USER_CONSTRUCTORS + + virtual ~Tuner(); + + // Standard EFX interface functions - do not change these declaration + virtual void update(); // main audio processing loop function + void mapMidiControl(int parameter, int midiCC, int midiChannel = 0) override; + void processMidi(int channel, int midiCC, int value) override; + void setParam(int paramIndex, float paramValue) override; + float getUserParamValue(int paramIndex, float normalizedParamValue); + const char* getName() override; + const uint8_t* getRblk() override; + + // control value set functions, must take floats between 0.0f and 1.0f - do not change these declarations + void volume(float value) override; + void tunersilent(float value); + void tunercents(float value); + void tunernote(float value); + + //!s - START_USER_PUBLIC_MEMBERS - put your public members below this line before the matching END + void bypass(bool byp) override; + //!e - END_USER_PUBLIC_MEMBERS + +private: + audio_block_t *m_inputQueueArray[1]; // required by AudioStream base class, array size is num inputs + int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping + + // m_bypass and m_volume are already provided by the base class AudioEffectWrapper + float m_tunersilent = 0.0f; + float m_tunercents = 0.0f; + float m_tunernote = 0.0f; + + audio_block_t* m_basicInputCheck(audio_block_t* inputAudioBlock, unsigned outputChannel); + + //!s - START_USER_PRIVATE_MEMBERS - put your private members below this line before the matching END + + // Tuner + // code excerpts from Teensy Audio Library analyze_notefreq.* + static constexpr unsigned AUDIO_GUITARTUNER_BLOCKS = 24; + uint64_t running_sum; + uint16_t tau_global; + uint64_t yin_buffer[5]; + uint64_t rs_buffer[5]; + int16_t AudioBuffer[AUDIO_GUITARTUNER_BLOCKS*128] __attribute__ ( ( aligned ( 4 ) ) ); + uint8_t yin_idx, state; + float periodicity, yin_threshold, cpu_usage_max, data; + bool next_buffer, first_run; + volatile bool new_output = false, process_buffer; + audio_block_t *blocklist1[AUDIO_GUITARTUNER_BLOCKS]; + audio_block_t *blocklist2[AUDIO_GUITARTUNER_BLOCKS]; + + bool m_initialized = false; + bool m_tunerEnabled = false; + bool m_silentEnabled = false; + unsigned m_skipUpdateCount = 0; + bool m_signalDetect = false; + float m_dcValueSum = 0.0f; + float m_dcValue = 0.0f; + unsigned m_dcValueCount = 0; + bool m_calibrateEnabled = false; + bool m_startupInProgress = true; + + + static constexpr float m_ALPHA = 0.95f; + static constexpr float m_ONE_MINUS_ALPHA = 1.0f - m_ALPHA; + float m_centsFiltered = 0.0f; + //SysPlatform::ElapsedMillis m_timer; + + bool available( void ); + float probability( void ); + float read( void ); + uint16_t estimate( uint64_t *yin, uint64_t *rs, uint16_t head, uint16_t tau ); + void process( void ); + void tunerReset(); + void init( float thresholdIn ); + static void copy_buffer(void *destination, const void *source); + + //!e - END_USER_PRIVATE_MEMBERS + +}; + +} diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..751553b --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +*.bak diff --git a/src/Tuner.cpp b/src/Tuner.cpp new file mode 100644 index 0000000..429239a --- /dev/null +++ b/src/Tuner.cpp @@ -0,0 +1,550 @@ +/* + * Company: Blackaddr Audio + * Effect Name: Noise Gate + * Description: A simple noise gate wtih controllable threshold, opening and closing times. + * + * This file was auto-generated by Aviate Audio Effect Creator for the Multiverse. + */ +#include +#include "Aviate/EfxPrint.h" +#include "Aviate/TunerManager.h" +#include "Tuner.h" + +using namespace Aviate; + +namespace BlackaddrAudio_Tuner { + +#define TUNER_A4STANDARD 440.0f +#define TUNER_FREQ_MIN 10.0f +#define TUNER_FREQ_MAX 1500.0f +#define TUNER_TOLERENCE 4.10f + +constexpr unsigned STARTUP_CALIB_TIME_SECONDS = 2; + +// NOTE FINDER PROTOTYPES +const char *noteToLabel (const uint8_t note); +static void noteBuildTable (const float A4); +const float noteToFreq (const uint8_t note, const uint8_t octave); +float noteFindNearest (float freqIn, uint8_t *noteOut, uint8_t *octaveOut); +float noteGetLower (uint8_t *note, uint8_t *octave); +float noteGetHigher (uint8_t *note, uint8_t *octave); +static void buildOctave (float pitchStandard, int octave); +static void noteBuildTable (const float A4); + +Tuner::Tuner() +: AudioStream(NUM_INPUTS, m_inputQueueArray) +{ + +} + +Tuner::~Tuner() +{ + +} + +void Tuner::update(void) +{ + if (!m_initialized) { init(0.1f); } + audio_block_t *inputAudioBlock = receiveWritable(); // get the next block of input samples + inputAudioBlock = m_basicInputCheck(inputAudioBlock, 0); // check for disable mode, bypass, or invalid inputs. Transmit to channel 0 in bypass + + if (!inputAudioBlock) { // EFX level bypass + // Tuner reset + for (unsigned i=0; i < AUDIO_GUITARTUNER_BLOCKS; i++) { + if (blocklist1[i]) { release(blocklist1[i]); blocklist1[i] = nullptr; } + if (blocklist2[i]) { release(blocklist2[i]); blocklist2[i] = nullptr; } + } + tunerReset(); + sendValueReport(TunerCents_e, -50.0f); + sendValueReport(TunerNote_e, static_cast(TunerNote::NO_DETECT)); + return; + } // no further processing for this update() call + + // You must call m_updateInputPeak() before processing the audio + m_updateInputPeak(inputAudioBlock); + + ////////////////////////////// + // START OF TUNER PROCESSING + ////////////////////////////// + if (m_tunerEnabled) { + audio_block_t *block = allocate(); + memcpy((void*)&block->data[0], (void*)&inputAudioBlock->data[0], sizeof(int16_t)*AUDIO_SAMPLES_PER_BLOCK); + + if ( next_buffer ) { + blocklist1[state++] = block; + if ( !first_run && process_buffer ) process( ); + } else { + blocklist2[state++] = block; + if ( !first_run && process_buffer ) process( ); + } + + if ( state >= AUDIO_GUITARTUNER_BLOCKS ) { + if ( next_buffer ) { + if ( !first_run && process_buffer ) process( ); + for ( unsigned i = 0; i < AUDIO_GUITARTUNER_BLOCKS; i++ ) copy_buffer( AudioBuffer+( i * 0x80 ), blocklist1[i]->data ); + for ( unsigned i = 0; i < AUDIO_GUITARTUNER_BLOCKS; i++ ) release( blocklist1[i] ); + next_buffer = false; + } else { + if ( !first_run && process_buffer ) process( ); + for ( unsigned i = 0; i < AUDIO_GUITARTUNER_BLOCKS; i++ ) copy_buffer( AudioBuffer+( i * 0x80 ), blocklist2[i]->data ); + for ( unsigned i = 0; i < AUDIO_GUITARTUNER_BLOCKS; i++ ) release( blocklist2[i] ); + next_buffer = true; + } + process_buffer = true; + first_run = false; + state = 0; + } + + bool updated = available(); + if (updated) { + float notef = read(); + if (notef < TUNER_FREQ_MIN) + notef = 0.01f; + else if (notef > TUNER_FREQ_MAX) + notef = TUNER_FREQ_MAX; + + // calc surrounding notes + uint8_t noteNearest; + uint8_t octaveNearest; + float delta = noteFindNearest(notef, ¬eNearest, &octaveNearest); + float freqN = noteToFreq(noteNearest, octaveNearest); + + uint8_t noteOutL = noteNearest; + uint8_t octaveOutL = octaveNearest; + float freqL = noteGetLower(¬eOutL, &octaveOutL); + + uint8_t noteOutH = noteNearest; + uint8_t octaveOutH = octaveNearest; + float freqH = noteGetHigher(¬eOutH, &octaveOutH); + + + // calc note cent off from nearest note + float cent = 0.0f; + if (delta > 0.0f) + cent = ((1.0f / (freqH - freqN)) * delta) * 100.0f; + else if (delta < 0.0f) + cent = -((1.0f / (freqL - freqN)) * delta) * 100.0f; + + //efxLogger.printf("Note: %s - cent: %f skipCount:%d\n", noteToLabel(noteNearest), cent, m_skipUpdateCount); + if (noteNearest < 0 || noteNearest > 11) { noteNearest = 12; } // 12 is the 'INVALID' note. + if (tunerManagerPtr) { tunerManagerPtr->sendNoteUpdate(static_cast(noteNearest), cent, !m_signalDetect); } + + float newCentValue; + if (cent < -50.0f) { newCentValue = -50.0f; } + else if (cent > 50.0f) { newCentValue = 50.0f; } + else { newCentValue = cent; } + m_tunercents = m_tunercents*m_ALPHA + newCentValue*m_ONE_MINUS_ALPHA; + + sendValueReport(TunerCents_e, m_tunercents); + sendValueReport(TunerNote_e, noteNearest); + m_signalDetect = true; + m_skipUpdateCount = 0; + + } else if (m_skipUpdateCount > (AUDIO_SAMPLE_RATE_HZ / AUDIO_SAMPLES_PER_BLOCK) / 4) { + if (tunerManagerPtr) { tunerManagerPtr->sendNoteUpdate(TunerNote::NO_DETECT, 0.0, true /* no filtering */); } + sendValueReport(TunerCents_e, -50.0f); + sendValueReport(TunerNote_e, static_cast(TunerNote::NO_DETECT)); + m_skipUpdateCount = 0; + m_signalDetect = false; + } else { + m_skipUpdateCount++; + } + } else { + // Tuner reset + for (unsigned i=0; i < AUDIO_GUITARTUNER_BLOCKS; i++) { + if (blocklist1[i]) { release(blocklist1[i]); blocklist1[i] = nullptr; } + if (blocklist2[i]) { release(blocklist2[i]); blocklist2[i] = nullptr; } + } + tunerReset(); + sendValueReport(TunerCents_e, -50.0f); + sendValueReport(TunerNote_e, static_cast(TunerNote::NO_DETECT)); + } + //////////////////////////// + // END OF TUNER PROCESSING + //////////////////////////// + + for (size_t i=0; i < AUDIO_SAMPLES_PER_BLOCK; i++) { + inputAudioBlock->data[i] = m_silentEnabled ? 0 : std::round((float)inputAudioBlock->data[i] * m_volume); + } + + m_updateOutputPeak(inputAudioBlock); // you must call m_upateOutputPeak() at the end of update() before transmit + transmit(inputAudioBlock); + release(inputAudioBlock); +} + +void Tuner::volume(float value) +{ + float volDbValue = -40.0f + (value * 50.0f); // remap the normalized value to represent -40dB to +10dB + volumeDb(volDbValue); // AudioEffectWrapper has built-in volume function in dB +} + +void Tuner::tunercents(float value) +{ + // this is a value monitor control, we ignore any incoming requests to set this parameter +} + +void Tuner::tunernote(float value) +{ + // this is a value monitor control, we ignore any incoming requests to set this parameter +} + +void Tuner::tunersilent(float value) +{ + m_tunersilent = value; + m_silentEnabled = m_tunersilent ? true : false; +} + +void Tuner::bypass(bool value) +{ + m_bypass = value; + m_tunerEnabled = !(value > 0.0f); + if (tunerManagerPtr) { tunerManagerPtr->setTunerMode(m_tunerEnabled); } +} + +////////// +// TUNER +////////// +#define HALF_BLOCKS AUDIO_GUITARTUNER_BLOCKS * 64 +#define NOTE_OCTAVES 6 // number of octaves covered + +enum _notes { + NOTE_C = 0, + NOTE_C_SHARP, + NOTE_D, + NOTE_D_SHARP, + NOTE_E, + NOTE_F, + NOTE_F_SHARP, + NOTE_G, + NOTE_G_SHARP, + NOTE_A, + NOTE_A_SHARP, + NOTE_B, + NO_DETECT +}; + +typedef struct { + uint8_t note; + char label[3]; + float freq[NOTE_OCTAVES]; +}notetable_t; + +static notetable_t notetable[12] = { + {NOTE_C, "C", {}}, + {NOTE_C_SHARP, "C#",{}}, + {NOTE_D, "D", {}}, + {NOTE_D_SHARP, "D#",{}}, + {NOTE_E, "E", {}}, + {NOTE_F, "F", {}}, + {NOTE_F_SHARP, "F#",{}}, + {NOTE_G, "G", {}}, + {NOTE_G_SHARP, "G#",{}}, + {NOTE_A, "A", {}}, + {NOTE_A_SHARP, "A#",{}}, + {NOTE_B, "B", {}}, +}; + +/** + * Copy internal blocks of data to class buffer + * + * @param destination destination address + * @param source source address + */ +void Tuner::copy_buffer(void *destination, const void *source) { + const uint16_t *src = ( const uint16_t * )source; + uint16_t *dst = ( uint16_t * )destination; + for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) *dst++ = (*src++); +} + +/** + * Start the Yin algorithm + * + * TODO: Significant speed up would be to use spectral domain to find fundamental frequency. + * This paper explains: https://aubio.org/phd/thesis/brossier06thesis.pdf -> Section 3.2.4 + * page 79. Might have to downsample for low fundmental frequencies because of fft buffer + * size limit. + */ +void Tuner::process( void ) { + + const int16_t *p; + p = AudioBuffer; + + uint16_t cycles = 64; + uint16_t tau = tau_global; + do { + uint16_t x = 0; + uint64_t sum = 0; + do { + int16_t current, lag, delta; + lag = *( ( int16_t * )p + ( x+tau ) ); + current = *( ( int16_t * )p+x ); + delta = ( current-lag ); + sum += delta * delta; + x += 4; + + lag = *( ( int16_t * )p + ( x+tau ) ); + current = *( ( int16_t * )p+x ); + delta = ( current-lag ); + sum += delta * delta; + x += 4; + + lag = *( ( int16_t * )p + ( x+tau ) ); + current = *( ( int16_t * )p+x ); + delta = ( current-lag ); + sum += delta * delta; + x += 4; + + lag = *( ( int16_t * )p + ( x+tau ) ); + current = *( ( int16_t * )p+x ); + delta = ( current-lag ); + sum += delta * delta; + x += 4; + } while ( x < HALF_BLOCKS ); + + uint64_t rs = running_sum; + rs += sum; + yin_buffer[yin_idx] = sum*tau; + rs_buffer[yin_idx] = rs; + running_sum = rs; + yin_idx = ( ++yin_idx >= 5 ) ? 0 : yin_idx; + tau = estimate( yin_buffer, rs_buffer, yin_idx, tau ); + + if ( tau == 0 ) { + process_buffer = false; + new_output = true; + yin_idx = 1; + running_sum = 0; + tau_global = 1; + return; + } + } while ( --cycles ); + + if ( tau >= HALF_BLOCKS ) { + process_buffer = false; + new_output = false; + yin_idx = 1; + running_sum = 0; + tau_global = 1; + return; + } + tau_global = tau; +} + +/** + * check the sampled data for fundamental frequency + * + * @param yin buffer to hold sum*tau value + * @param rs buffer to hold running sum for sampled window + * @param head buffer index + * @param tau lag we are currently working on gets incremented + * + * @return tau + */ +uint16_t Tuner::estimate( uint64_t *yin, uint64_t *rs, uint16_t head, uint16_t tau ) { + const uint64_t *y = ( uint64_t * )yin; + const uint64_t *r = ( uint64_t * )rs; + uint16_t _tau, _head; + const float thresh = yin_threshold; + _tau = tau; + _head = head; + + if ( _tau > 4 ) { + + uint16_t idx0, idx1, idx2; + idx0 = _head; + idx1 = _head + 1; + idx1 = ( idx1 >= 5 ) ? 0 : idx1; + idx2 = head + 2; + idx2 = ( idx2 >= 5 ) ? 0 : idx2; + + float s0, s1, s2; + s0 = ( ( float )*( y+idx0 ) / *( r+idx0 ) ); + s1 = ( ( float )*( y+idx1 ) / *( r+idx1 ) ); + s2 = ( ( float )*( y+idx2 ) / *( r+idx2 ) ); + + if ( s1 < thresh && s1 < s2 ) { + uint16_t period = _tau - 3; + periodicity = 1 - s1; + data = period + 0.5f * ( s0 - s2 ) / ( s0 - 2.0f * s1 + s2 ); + return 0; + } + } + return _tau + 1; +} + +void Tuner::tunerReset() { + process_buffer = false; + periodicity = 0.0f; + next_buffer = true; + running_sum = 0; + tau_global = 1; + first_run = true; + yin_idx = 1; + state = 0; + data = 0.0f; +} + +void Tuner::init( float thresholdIn ) { + tunerReset(); + + yin_threshold = thresholdIn; + process_buffer = false; + + noteBuildTable(TUNER_A4STANDARD); + + m_initialized = true; +} + +/** + * available + * + * @return true if data is ready else false + */ +bool Tuner::available( void ) { + bool flag = new_output; + if ( flag ) new_output = false; + return flag; +} + +/** + * read processes the data samples for the Yin algorithm. + * + * @return frequency in hertz + */ +float Tuner::read( void ) { + float d = data; + return AUDIO_SAMPLE_RATE_HZ / d; +} + +/** + * Periodicity of the sampled signal from Yin algorithm from read function. + * + * @return periodicity + */ +float Tuner::probability( void ) { + float p = periodicity; + return p; +} + +// NOTE FINDER +const char *noteToLabel (const uint8_t note) +{ + return notetable[note].label; +} + +const float noteToFreq (const uint8_t note, const uint8_t octave) +{ + return notetable[note].freq[octave]; +} + +float noteFindNearest (float freqIn, uint8_t *noteOut, uint8_t *octaveOut) +{ + float delta = 999999.0f; + uint8_t note = -1; + uint8_t octave = -1; + + for (int n = 0; n < 12; n++){ + for (int o = 0; o < NOTE_OCTAVES; o++){ + const float diff = fabsf(noteToFreq(n, o) - freqIn); + if (diff < delta){ + delta = diff; + note = n; + octave = o; + } + } + } + + if (note != -1){ + *noteOut = note; + *octaveOut = octave; + + if (freqIn > noteToFreq(note, octave)) + return delta; + else + return -delta; + }else{ + *noteOut = -1; + return 0.0f; + } +} + +float noteGetLower (uint8_t *note, uint8_t *octave) +{ + int8_t octaveLower = *octave; + int8_t noteLower = *note - 1; + + if (noteLower < NOTE_C){ + noteLower = NOTE_B; + if (--octaveLower < 0) + noteLower = -1; + } + + if (noteLower != -1){ + float freq = noteToFreq(noteLower, octaveLower); + *note = noteLower; + *octave = octaveLower; + return freq; + } + + return 0.0f; +} + +float noteGetHigher (uint8_t *note, uint8_t *octave) +{ + int8_t octaveHigher = *octave; + int8_t noteHigher = *note + 1; + + if (noteHigher > NOTE_B){ + noteHigher = NOTE_C; + if (++octaveHigher >= NOTE_OCTAVES){ + octaveHigher = -1; + noteHigher = -1; + } + } + + if (noteHigher != -1){ + float freq = noteToFreq(noteHigher, octaveHigher); + *note = noteHigher; + *octave = octaveHigher; + return freq; + } + + return 0.0f; +} + +static void buildOctave (float pitchStandard, int octave) +{ + octave -= 4; + if (octave < -4){ + return; + }else if (octave < 0){ + for (int d = octave+1; d <= 0; d++) + pitchStandard /= 2.0f; + }else if (octave > 0){ + for (int d = 0; d < octave; d++) + pitchStandard *= 2.0f; + } + + int noteIdx = 11; + + // A# and B + for (float n = 2; n >= 1; n -= 1.0f){ + float freq = pitchStandard * exp2f(n/12.0f); + notetable[noteIdx--].freq[octave+4] = freq; + } + + // C to A + for (float n = 0; n <= 9; n += 1.0f){ + float freq = pitchStandard / exp2f(n/12.0f); + notetable[noteIdx--].freq[octave+4] = freq; + } +} + +static void noteBuildTable (const float A4) +{ + for (int o = 0; o < NOTE_OCTAVES; o++){ + buildOctave(A4, o); + } +} + +} diff --git a/src/TunerBase.cpp b/src/TunerBase.cpp new file mode 100644 index 0000000..1729061 --- /dev/null +++ b/src/TunerBase.cpp @@ -0,0 +1,127 @@ +/* + * Company: Blackaddr Audio + * Effect Name: Tuner + * Description: A noise gate wtih controllable threshold, opening and closing times and DC removal filter. Basic tuner included. + * + * This file was auto-generated by Aviate Audio Effect Creator for the Multiverse. + */ +#include +#include "Aviate/LibBasicFunctions.h" +#include "Tuner.h" + +using namespace Aviate; + +namespace BlackaddrAudio_Tuner { + +void Tuner::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; +} + +void Tuner::setParam(int paramIndex, float paramValue) +{ + switch(paramIndex) { + case 0 : bypass( (paramValue - 0.000000) / (1.000000 - 0.000000) ); break; + case 1 : volume( (paramValue - 0.000000) / (10.000000 - 0.000000) ); break; + case 2 : tunersilent( (paramValue - 0.000000) / (1.000000 - 0.000000) ); break; + case 3 : tunercents( (paramValue - -50.000000) / (50.000000 - -50.000000) ); break; + case 4 : tunernote( (paramValue - 0.000000) / (12.000000 - 0.000000) ); break; + default : break; + } +} + +float Tuner::getUserParamValue(int paramIndex, float normalizedParamValue) +{ + switch(paramIndex) { + case 0 : return ( ((1.000000 - 0.000000) * normalizedParamValue) + 0.000000 ); // bypass + case 1 : return ( ((10.000000 - 0.000000) * normalizedParamValue) + 0.000000 ); // volume + case 2 : return ( ((1.000000 - 0.000000) * normalizedParamValue) + 0.000000 ); // tunersilent + case 3 : return ( ((50.000000 - -50.000000) * normalizedParamValue) + -50.000000 ); // tunercents + case 4 : return ( ((12.000000 - 0.000000) * normalizedParamValue) + 0.000000 ); // tunernote + default : return 0.0f; + } +} + +void Tuner::processMidi(int channel, int control, int value) +{ + float val = (float)value / 127.0f; + + if ((m_midiConfig[Bypass_e][MIDI_CHANNEL] == channel) && (m_midiConfig[Bypass_e][MIDI_CONTROL] == control)) { + bypass(val); + return; + } + + if ((m_midiConfig[Volume_e][MIDI_CHANNEL] == channel) && (m_midiConfig[Volume_e][MIDI_CONTROL] == control)) { + volume(val); + return; + } + + if ((m_midiConfig[TunerSilent_e][MIDI_CHANNEL] == channel) && (m_midiConfig[TunerSilent_e][MIDI_CONTROL] == control)) { + tunersilent(val); + return; + } + + if ((m_midiConfig[TunerCents_e][MIDI_CHANNEL] == channel) && (m_midiConfig[TunerCents_e][MIDI_CONTROL] == control)) { + tunercents(val); + return; + } + + if ((m_midiConfig[TunerNote_e][MIDI_CHANNEL] == channel) && (m_midiConfig[TunerNote_e][MIDI_CONTROL] == control)) { + tunernote(val); + return; + } + +} + +audio_block_t* Tuner::m_basicInputCheck(audio_block_t* inputAudioBlock, unsigned outputChannel) +{ + // Check if effect is disabled + if (m_enable == false) { + // do not transmit or process any audio, return as quickly as possible after releasing the inputs + if (inputAudioBlock) { release(inputAudioBlock); } + return nullptr; // disabled, no further EFX processing in update() + } // end of enable check + + // check if effect is in bypass + if (m_bypass == true) { + // drive input directly to the specified output. ie. bypass + if (inputAudioBlock != nullptr) { + // valid input, drive to outputChannel if specified + if (outputChannel >= 0) { + transmit(inputAudioBlock, outputChannel); // drive to specified output + } + release(inputAudioBlock); // release the input block as we are done with it + } else { // invalid input block, allocate a block and drive silence if specified + if (outputChannel >= 0) { + audio_block_t* silenceBlock = allocate(); + if (silenceBlock) { + clearAudioBlock(silenceBlock); // create silence in the buffer + transmit(silenceBlock, outputChannel); + release(silenceBlock); + } + } + } + return nullptr; // bypassed, no further EFX processing in update() + } // end of bypass check + + // If not disabled or bypassed, create silence if the input block is invalid then + // return the valid audio block so update() can continue. + if (inputAudioBlock == nullptr) { + inputAudioBlock = allocate(); + if (inputAudioBlock == nullptr) { return nullptr; } // check if allocate was unsuccessful + // else + clearAudioBlock(inputAudioBlock); + } + return inputAudioBlock; // inputAudioBLock is valid and ready for update() processing +} + +const uint8_t rblk[256] = TEENSY_AUDIO_BLOCK; +const uint8_t* Tuner::getRblk() { return rblk; } +static constexpr char PROGMEM Tuner_name[] = {0x42, 0x6c, 0x61, 0x63, 0x6b, 0x61, 0x64, 0x64, 0x72, 0x20, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x3a, 0x54, 0x75, 0x6e, 0x65, 0x72, 0x0}; +const char* Tuner::getName() { return Tuner_name; } + +}