Simplest Ukulele Tuner

Just realized that you can use your Arduboy as a tuner for musical instruments. Example for ukulele:

#include <Arduboy2.h>
#include <ArduboyTones.h>

Arduboy2 arduboy;
ArduboyTones sound(arduboy.audio.enabled);

enum {
  WAIT_MS  = 2000
};

void setup() {
  arduboy.begin();
  arduboy.audio.on();
}

void loop() {
  // Ukulele tuner with Aquilla Kids string colors on LED
  arduboy.digitalWriteRGB(RGB_OFF,RGB_ON,RGB_OFF);
  sound.tone(NOTE_G4,WAIT_MS);
  arduboy.delayShort(WAIT_MS);
  arduboy.digitalWriteRGB(RGB_ON,RGB_OFF,RGB_OFF);
  sound.tone(NOTE_C4,WAIT_MS);
  arduboy.delayShort(WAIT_MS);
  arduboy.digitalWriteRGB(RGB_ON,RGB_ON,RGB_OFF);
  sound.tone(NOTE_E4,WAIT_MS);
  arduboy.delayShort(WAIT_MS);
  arduboy.digitalWriteRGB(RGB_OFF,RGB_OFF,RGB_ON);
  sound.tone(NOTE_A4,WAIT_MS);
  arduboy.delayShort(WAIT_MS);
}
3 Likes

It might be easier to use if you set it up to use the buttons to select notes, like so:

#include <Arduboy2.h>
#include <ArduboyTones.h>

Arduboy2 arduboy;
ArduboyTones tones(arduboy.audio.enabled);

// Has to be one line because Arduino's ino processor is really stupid
template<typename Type, size_t arraySize> constexpr size_t size(const Type (&)[arraySize]) noexcept
{
	return arraySize;
}

using FlashStringHelper = const __FlashStringHelper *;

inline FlashStringHelper readFlashStringPointer(const char * const * flashString) noexcept
{
	return static_cast<FlashStringHelper>(pgm_read_ptr(flashString));
}

constexpr uint16_t millisecondsPerSecond = 1000;
constexpr uint16_t noteDuration = 2 * millisecondsPerSecond;

constexpr uint16_t notes[] PROGMEM
{
	NOTE_G4,
	NOTE_C4,
	NOTE_E4,
	NOTE_A4,
};

constexpr size_t firstNoteIndex = 0;
constexpr size_t lastNoteIndex = (size(notes) - 1);

constexpr char noteName0[] PROGMEM = "G4";
constexpr char noteName1[] PROGMEM = "C4";
constexpr char noteName2[] PROGMEM = "E4";
constexpr char noteName3[] PROGMEM = "A4";

// Make sure the length of noteNames and notes are kept in sync
constexpr const char * noteNames[] PROGMEM
{
	noteName0,
	noteName1,
	noteName2,
	noteName3,
};

void setup()
{
	arduboy.begin();
}

size_t noteIndex = 0;

void loop()
{
	if(!arduboy.nextFrame())
		return;

	arduboy.pollButtons();
	
	// Play note
	if(arduboy.justPressed(A_BUTTON))
	{
		ArduboyTones::noTone();
		ArduboyTones::tone(pgm_read_word(&notes[noteIndex]), noteDuration);
	}
	
	// Silence
	if(arduboy.justPressed(B_BUTTON))
	{
		ArduboyTones::noTone();
	}
	
	// Previous note
	if(arduboy.justPressed(UP_BUTTON))
	{
		if(noteIndex > firstNoteIndex)
			--noteIndex;
		else
			noteIndex = lastNoteIndex;
	}
	
	// Next note
	if(arduboy.justPressed(DOWN_BUTTON))
	{
		if(noteIndex < lastNoteIndex)
			++noteIndex;
		else
			noteIndex = firstNoteIndex;
	}
	
	arduboy.clear();
	
	arduboy.println(readFlashStringPointer(&noteNames[noteIndex]));
	
	arduboy.display();
}

size function borrowed from my size demo.
readFlashStringPointer borrowed from my flash string demo.


InstrumentTuning.hex (21.9 KB)

1 Like

@Pharap your code removes the main feature of the original post - presenting the colour of the ukelele string being tuned via the LEDs.

2 Likes

Can anyone give me a link with explanation when i need to use FlashStringHelper and when not? Meanwhile i rewrited the code:

#include <Arduboy2.h>
#include <ArduboyTones.h>

Arduboy2 arduboy;
ArduboyTones sound(arduboy.audio.enabled);

template<typename Type, size_t arraySize> constexpr size_t size(const Type (&)[arraySize]) noexcept {
  return arraySize;
}

constexpr size_t x = 32;
constexpr size_t y = 16;

inline void tuneGreenString() noexcept {
    arduboy.clear();
    arduboy.setCursor(x,y);
    arduboy.print(F("G4"));
    arduboy.digitalWriteRGB(RGB_OFF,RGB_ON,RGB_OFF);
    sound.tone(NOTE_G4);
    arduboy.display();
}

inline void tuneRedString() noexcept {
    arduboy.clear();
    arduboy.setCursor(x,y);
    arduboy.print(F("C4"));
    arduboy.digitalWriteRGB(RGB_ON,RGB_OFF,RGB_OFF);
    sound.tone(NOTE_C4);
    arduboy.display();
}

inline void tuneYellowString() noexcept {
    arduboy.clear();
    arduboy.setCursor(x,y);
    arduboy.print(F("E4"));
    arduboy.digitalWriteRGB(RGB_ON,RGB_ON,RGB_OFF);
    sound.tone(NOTE_E4);
    arduboy.display();
}

inline void tuneBlueString() noexcept {
    arduboy.clear();
    arduboy.setCursor(x,y);
    arduboy.print(F("A4"));
    arduboy.digitalWriteRGB(RGB_OFF,RGB_OFF,RGB_ON);
    sound.tone(NOTE_A4);
    arduboy.display();
}

void (*tunings[])() = {
  tuneGreenString,
  tuneRedString,
  tuneYellowString,
  tuneBlueString
};

constexpr size_t firstString = 0;
constexpr size_t lastString = size(tunings) - 1;

size_t currentString = 0;

void setup() {
  arduboy.begin();
  arduboy.audio.on();
  arduboy.setTextSize(5);
  arduboy.setFrameRate(15);
  sound.volumeMode(VOLUME_ALWAYS_HIGH);
  tuneGreenString();
}

void loop() {
  // Ukulele tuner with Aquilla Kids string colors on LED
  if(!arduboy.nextFrame())
    return;
  arduboy.pollButtons();
  if(arduboy.justPressed(A_BUTTON)) {
    arduboy.audio.on();
    tuneGreenString();
    arduboy.invert(false);
  }
  if(arduboy.justPressed(B_BUTTON)) {
    arduboy.digitalWriteRGB(RGB_OFF,RGB_OFF,RGB_OFF);
    arduboy.invert(true);
    arduboy.audio.off();
  }
  if(arduboy.justPressed(UP_BUTTON) || arduboy.justPressed(RIGHT_BUTTON)) {
    if(currentString > firstString) {
      --currentString;
    } else {
      currentString = lastString;
    }
    (*tunings[currentString])();
  }
  if(arduboy.justPressed(DOWN_BUTTON) || arduboy.justPressed(LEFT_BUTTON)) {
    if(currentString < lastString) {
      ++currentString;
    } else {
      currentString = firstString;
    }
    (*tunings[currentString])();
  }
  arduboy.idle();
}
1 Like

Fair point. Not sure how I overlooked those,
but I threw the code together in about 5-10 minutes so it’s hardly surprising.

At any rate I didn’t even know ukeleles have mult-coloured strings.
All I know about ukeleles is how to pronounce the name correctly.
(I very briefly dabbled in learning a bit of Hawaiian. Interesting language.)


Unfortunately I don’t have one bookmarked so I’ll write another from scratch.

Basically the Arduboy uses an AVR chip, and AVR chips have three kinds of memory:

  • RAM - which is readable and writable, but volatile (which means the data vanishes when the power is turned off)
  • PROGMEM (or ‘Flash’) - which can be read byte-by-byte but can only be written in large blocks, and is non-volatile (the data stays after the power is cut). This is where the machine code that tells the CPU what to do is stored, and you can opt to manually store and read data from it using the PROGMEM and pgm_read_xxx macros (where xxx is byte, word, dword, float or ptr)
  • EEPROM - which is readable, writable and non-volatile, and is where people store save data

Each one has a different mechanism for being read from, and when you pass a string to print it has no way of knowing whether the string is in RAM or PROGMEM, so it assumes the string is in RAM.

The flash string helper trick is a solution that the Arduino library writers came up with to tell the print functions when a string is in progmem.

However, the Arduino library itself only provides the F macro, which only works with string literals (e.g. arduboy.print(F("Hello"));), hence I wrote my flash string helper demo to provide a set of functions that makes it possible to use the flash string helper trick with strings that are already stored in progmem but aren’t string literals.

(There’s actually a better solution than the flash string pointer trick that Arduino came up with,
but we’re kind of stuck with it now.)


A small tip/bit of trivia: you don’t actually need the * when using a function pointer.
If you try to dereference a function pointer, you actually get the same pointer back, so tunings[currentString], *tunings[currentString] and ********tunings[currentString] all evaluate to the same value.
(This only applies to function pointers, not to other pointers.)

Suprisingly not many people know about this, so it’s not uncommon to find people trying to dereference function pointers before calling them.

1 Like

In a program your size, you really do not need FlashStringHelper.

@Pharap has included the four note names in an array and the helper allows him to retrieve them from that array in a format that print() expects. Edit: I see he and I responded at about the same time. Refer above for details!

1 Like

LOL … 4 of the 12 lines in loop are dedicated to LEDs so easy to miss.

I am not sure common colour strings are - probably just for kids versions of the Uke.