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; } |
||||
|
||||
} |