After Width: | Height: | Size: 137 KiB |
@ -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 |
||||||
|
} |
||||||
|
} |
@ -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. |
@ -0,0 +1 @@ |
|||||||
|
*.efx |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 1.9 MiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 5.0 MiB |
After Width: | Height: | Size: 6.9 MiB |
After Width: | Height: | Size: 23 KiB |
@ -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 <Audio.h> |
||||||
|
#include <arm_math.h> |
||||||
|
#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
|
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
*.bak |
@ -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 <cmath> |
||||||
|
#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<unsigned>(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<Aviate::TunerNote>(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<unsigned>(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<unsigned>(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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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 <cmath> |
||||||
|
#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; } |
||||||
|
|
||||||
|
} |