Skip to content

Commit

Permalink
#17 Reintroduced buzzer
Browse files Browse the repository at this point in the history
Used the birthday experiment code, so #18 can be closed once this is merged
  • Loading branch information
David032 committed Nov 26, 2023
1 parent ad9d26e commit 3897ea9
Show file tree
Hide file tree
Showing 2 changed files with 367 additions and 0 deletions.
361 changes: 361 additions & 0 deletions skullOS.Modules/Buzzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,361 @@
using skullOS.HardwareServices;
using skullOS.Modules.Interfaces;
using static skullOS.Modules.BuzzerLibrary;
using static skullOS.Modules.BuzzerStructures;
using DeviceBuzzer = Iot.Device.Buzzer.Buzzer;
using Duration = skullOS.Modules.BuzzerStructures.Duration;

namespace skullOS.Modules
{
public class Buzzer : Module, IBuzzerModule
{
BuzzerService PwmBuzzer;
MelodyPlayer Player;
public Buzzer()
{
PwmBuzzer = new BuzzerService(13);
Player = new MelodyPlayer(PwmBuzzer.Buzzer);
}

public void PlayTune(Tunes tuneToPlay)
{
switch (tuneToPlay)
{
case Tunes.HappyBirthday:
logger.LogMessage("Playing Happy Birthday on the buzzer");
Player.Play(happyBirthday, 100);
break;
case Tunes.AlphabetSong:
logger.LogMessage("Playing the alphabet song on the buzzer");
Player.Play(alphabetSong, 100);
break;
}
}

public override void OnAction(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
public override void OnEnable(string[] args)
{
throw new NotImplementedException();
}
public override string ToString()
{
return "Buzzer";
}
}

#region Buzzer Media Classes
/// <summary>
/// 3rd party class for playing media via a buzzer
/// Sourced from teh iot samples
/// </summary>
internal class MelodyPlayer
{
private readonly DeviceBuzzer _buzzer;
private int _wholeNoteDurationInMilliseconds;

/// <summary>
/// Create MelodyPlayer.
/// </summary>
/// <param name="buzzer">Buzzer instance to be played on.</param>
public MelodyPlayer(DeviceBuzzer buzzer) => _buzzer = buzzer;

/// <summary>
/// Play melody elements sequecne.
/// </summary>
/// <param name="sequence">Sequence of pauses and notes elements to be played.</param>
/// <param name="tempo">Tempo of melody playing.</param>
/// <param name="tonesToTranspose">Tones to transpose</param>
public void Play(IList<MelodyElement> sequence, int tempo, int tonesToTranspose = 0)
{
_wholeNoteDurationInMilliseconds = GetWholeNoteDurationInMilliseconds(tempo);
sequence = TransposeSequence(sequence, tonesToTranspose);
foreach (var element in sequence)
{
PlayElement(element);
}
}

private static IList<MelodyElement> TransposeSequence(IList<MelodyElement> sequence, int tonesToTranspose)
{
if (tonesToTranspose == 0)
{
return sequence;
}

return sequence
.Select(element => TransposeElement(element, tonesToTranspose))
.ToList();
}

private static MelodyElement TransposeElement(MelodyElement element, int tonesToTransponse)
{
if (element is not NoteElement noteElement)
{
return element;
}

// Every octave consists of 12 notes numberd from 1 to 12. Each octave numbered from 1 to 8.
// To transpose over octaves we will get absolute index of note like (octave index - 1) * 12 + (note index - 1).
// As far as octave and note numbered starting from 1 we decrease it's values by 1 for farher calculation.
var absoluteNoteNumber = ((int)noteElement.Octave - 1) * 12 + ((int)noteElement.Note - 1);

// Then we transpose absolute number. In case gotten value exceeds maximum value of 96 (12 notes * 8 octave)
// we calculate remainder of dividing by 96. In case gotten value is below 0 we add 96.
absoluteNoteNumber = (absoluteNoteNumber + tonesToTransponse) % 96;
absoluteNoteNumber = absoluteNoteNumber >= 0 ? absoluteNoteNumber : absoluteNoteNumber + 96;

// Then to get transposed octave index we divide transposed absolute index
// value by 12 and increase it by 1 due to we decreased it by 1 before.
// To get transposed note index we will get remainder of dividing by 12
// and increase it by 1 due to we decreased it by 1 before.
noteElement.Octave = (Octave)(absoluteNoteNumber / 12 + 1);
noteElement.Note = (Note)(absoluteNoteNumber % 12 + 1);
return noteElement;
}

private void PlayElement(MelodyElement element)
{
int durationInMilliseconds = _wholeNoteDurationInMilliseconds / (int)element.Duration;

if (element is not NoteElement noteElement)
{
// In case it's a pause element we have only just wait desired time.
Thread.Sleep(durationInMilliseconds);
}
else
{
// In case it's a note element we play it.
var frequency = GetFrequency(noteElement.Note, noteElement.Octave);
_buzzer.PlayTone(frequency, (int)(durationInMilliseconds * 0.7));
Thread.Sleep((int)(durationInMilliseconds * 0.3));
}
}

private static readonly Dictionary<Note, double> notesOfEightOctaveToFrequenciesMap
= new Dictionary<Note, double>
{
{ Note.C, 4186.01 },
{ Note.Db, 4434.92 },
{ Note.D, 4698.63 },
{ Note.Eb, 4978.03 },
{ Note.E, 5274.04 },
{ Note.F, 5587.65 },
{ Note.Gb, 5919.91 },
{ Note.G, 6271.93 },
{ Note.Ab, 6644.88 },
{ Note.A, 7040.00 },
{ Note.Bb, 7458.62 },
{ Note.B, 7902.13 }
};

// In music tempo defines amount of quarter notes per minute.
// Dividing minute (60 * 1000) by tempo we get duration of quarter note.
// Whole note duration equals to four quarters.
private static int GetWholeNoteDurationInMilliseconds(int tempo) => 4 * 60 * 1000 / tempo;

private static double GetFrequency(Note note, Octave octave)
{
// We could decrease octave of every note by 1 by dividing it's frequency by 2.
// We have predefined frequency of every note of eighth octave rounded to 2 decimals.
// To get note frequency of any other octave we have to divide note frequency of eighth octave by 2 n times,
// where n is a difference between eight octave and desired octave.
var eightOctaveNoteFrequency = GetNoteFrequencyOfEightOctave(note);
var frequencyDivider = Math.Pow(2, 8 - (int)octave);
return Math.Round(eightOctaveNoteFrequency / frequencyDivider, 2);
}

private static double GetNoteFrequencyOfEightOctave(Note note)
{
if (notesOfEightOctaveToFrequenciesMap.TryGetValue(note, out double result))
{
return result;
}

return 0;
}

public void Dispose() => _buzzer?.Dispose();
}

/// <summary>
/// Musical data for the buzzer media player
/// </summary>
public static class BuzzerLibrary
{
public enum Tunes
{
HappyBirthday,
AlphabetSong,
}

public static IList<MelodyElement> alphabetSong = new List<MelodyElement>()
{
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), // A
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), // B
new NoteElement(Note.G, Octave.Fourth, Duration.Quarter), // C
new NoteElement(Note.G, Octave.Fourth, Duration.Quarter), // D
new NoteElement(Note.A, Octave.Fourth, Duration.Quarter), // E
new NoteElement(Note.A, Octave.Fourth, Duration.Quarter), // F
new NoteElement(Note.G, Octave.Fourth, Duration.Half), // G
new NoteElement(Note.F, Octave.Fourth, Duration.Quarter), // H
new NoteElement(Note.F, Octave.Fourth, Duration.Quarter), // I
new NoteElement(Note.E, Octave.Fourth, Duration.Quarter), // J
new NoteElement(Note.E, Octave.Fourth, Duration.Quarter), // K
new NoteElement(Note.D, Octave.Fourth, Duration.Eighth), // L
new NoteElement(Note.D, Octave.Fourth, Duration.Eighth), // M
new NoteElement(Note.D, Octave.Fourth, Duration.Eighth), // N
new NoteElement(Note.D, Octave.Fourth, Duration.Eighth), // O
new NoteElement(Note.C, Octave.Fourth, Duration.Half), // P
new NoteElement(Note.G, Octave.Fourth, Duration.Quarter), // Q
new NoteElement(Note.G, Octave.Fourth, Duration.Quarter), // R
new NoteElement(Note.F, Octave.Fourth, Duration.Half), // S
new NoteElement(Note.E, Octave.Fourth, Duration.Quarter), // T
new NoteElement(Note.E, Octave.Fourth, Duration.Quarter), // U
new NoteElement(Note.D, Octave.Fourth, Duration.Half), // V
new NoteElement(Note.G, Octave.Fourth, Duration.Eighth), // Dou-
new NoteElement(Note.G, Octave.Fourth, Duration.Eighth), // ble
new NoteElement(Note.G, Octave.Fourth, Duration.Quarter), // U
new NoteElement(Note.F, Octave.Fourth, Duration.Half), // X
new NoteElement(Note.E, Octave.Fourth, Duration.Quarter), // Y
new NoteElement(Note.E, Octave.Fourth, Duration.Quarter), // and
new NoteElement(Note.D, Octave.Fourth, Duration.Half), // Z
};
public static IList<MelodyElement> happyBirthday = new List<MelodyElement>()
{
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //Hap
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //-py
new NoteElement(Note.D, Octave.Fourth, Duration.Quarter), //birth
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //day
new NoteElement(Note.F, Octave.Fourth, Duration.Quarter), //to
new NoteElement(Note.E, Octave.Fourth, Duration.Half), //you
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //Hap
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //-py
new NoteElement(Note.D, Octave.Fourth, Duration.Quarter), //birth
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //day
new NoteElement(Note.G, Octave.Fourth, Duration.Quarter), //to
new NoteElement(Note.F, Octave.Fourth, Duration.Half), //you
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //Hap
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //-py
new NoteElement(Note.C, Octave.Fourth, Duration.Quarter), //birth
new NoteElement(Note.A, Octave.Fourth, Duration.Quarter), //day
new NoteElement(Note.F, Octave.Fourth, Duration.Quarter), //dear
new NoteElement(Note.E, Octave.Fourth, Duration.Quarter), //mak
new NoteElement(Note.D, Octave.Fourth, Duration.Quarter), //-er
new NoteElement(Note.B, Octave.Fourth, Duration.Quarter), //Hap
new NoteElement(Note.B, Octave.Fourth, Duration.Quarter), //-py
new NoteElement(Note.A, Octave.Fourth, Duration.Quarter), //birth
new NoteElement(Note.F, Octave.Fourth, Duration.Quarter), //-day
new NoteElement(Note.G, Octave.Fourth, Duration.Quarter), //to
new NoteElement(Note.F, Octave.Fourth, Duration.Half), //you
};
}

/// <summary>
/// Musical structures used by the buzzer media player
/// </summary>
public static class BuzzerStructures
{
/// <summary>
/// Represents all twelve notes.
/// </summary>
internal enum Note
{
C = 1,
Db = 2,
D = 3,
Eb = 4,
E = 5,
F = 6,
Gb = 7,
G = 8,
Ab = 9,
A = 10,
Bb = 11,
B = 12,
}

/// <summary>
/// Represents music octave.
/// </summary>
internal enum Octave
{
First = 1,
Second = 2,
Third = 3,
Fourth = 4,
Fifth = 5,
Sixth = 6,
Seventh = 7,
Eighth = 8
}

/// <summary>
/// Represents music note duration.
/// </summary>
internal enum Duration
{
Whole = 1,
Half = 2,
Quarter = 4,
Eighth = 8,
Sixteenth = 16,
}
}

/// <summary>
/// A base class for melody sequence elements.
/// </summary>
public abstract class MelodyElement
{
/// <summary>
/// Duration which defines how long should element take on melody sequence timeline.
/// </summary>
public Duration Duration { get; set; }

public MelodyElement(Duration duration) => Duration = duration;
}

/// <summary>
/// Note element to define Note and Octave of sound in melody.
/// </summary>
internal class NoteElement : MelodyElement
{
/// <summary>
/// Note of sound in melody sequence.
/// </summary>
public Note Note { get; set; }

/// <summary>
/// Octave of sound in melody sequence.
/// </summary>
public Octave Octave { get; set; }

/// <summary>
/// Create Note element.
/// </summary>
/// <param name="note">Note of sound.</param>
/// <param name="octave">Octave of sound.</param>
/// <param name="duration">Duration of sound in melody sequence timeline.</param>
public NoteElement(Note note, Octave octave, Duration duration)
: base(duration)
{
Note = note;
Octave = octave;
}
}

/// <summary>
/// A pause
/// </summary>
internal class PauseElement : MelodyElement
{
public PauseElement(Duration duration) : base(duration)
{

}
}
#endregion
}
6 changes: 6 additions & 0 deletions skullOS.Modules/Interfaces/IBuzzerModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace skullOS.Modules.Interfaces
{
internal interface IBuzzerModule
{
}
}

0 comments on commit 3897ea9

Please sign in to comment.