diff --git a/Source/Meadow.Foundation.Core/Speakers/PiezoSpeaker.cs b/Source/Meadow.Foundation.Core/Speakers/PiezoSpeaker.cs index 62ce37ea3f..68447f2146 100644 --- a/Source/Meadow.Foundation.Core/Speakers/PiezoSpeaker.cs +++ b/Source/Meadow.Foundation.Core/Speakers/PiezoSpeaker.cs @@ -11,6 +11,12 @@ namespace Meadow.Foundation.Audio /// public class PiezoSpeaker : IToneGenerator { + /// + /// The volume from 0-1 + /// Defined by the PWM port duty cycle from 0 to 0.5 + /// + public float Volume { get; protected set; } = 1.0f; + /// /// Gets the port that is driving the Piezo Speaker /// @@ -72,7 +78,7 @@ public async Task PlayTone(Frequency frequency, TimeSpan duration) isPlaying = true; Port.Frequency = frequency; - Port.DutyCycle = 0.5f; + Port.DutyCycle = Volume / 2f; if (duration.TotalMilliseconds > 0) { @@ -91,5 +97,19 @@ public void StopTone() { Port.DutyCycle = 0f; } + + /// + /// Set the playback volume + /// + /// The volume from 0 (off) to 1 (max volume) + public void SetVolume(float volume) + { + Volume = Math.Clamp(volume, 0, 1); + + if(isPlaying) + { + Port.DutyCycle = Volume / 2f; + } + } } } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Audio.MicroAudio.csproj b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Audio.MicroAudio.csproj new file mode 100644 index 0000000000..148d246885 --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Audio.MicroAudio.csproj @@ -0,0 +1,22 @@ + + + true + Wilderness Labs, Inc + netstandard2.1 + Library + MicroAudio + Wilderness Labs, Inc + http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/ + Meadow.Foundation.Audio.MicroAudio + icon.png + https://github.com/WildernessLabs/Meadow.Foundation + Meadow,Meadow.Foundation,Audio,Songs,Tone,Tones,Music,Sound,Effects + 0.1.0 + true + Lightweight single-voice sound effect and music player designed for embedded applications + + + + + + diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/GameSounds.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/GameSounds.cs new file mode 100644 index 0000000000..3c9dbc211b --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/GameSounds.cs @@ -0,0 +1,325 @@ +using Meadow.Peripherals.Speakers; +using Meadow.Units; +using System; +using System.Threading.Tasks; + +namespace Meadow.Foundation.Audio +{ + /// + /// A class for playing game sounds using an IToneGenerator + /// + public class GameSounds + { + private readonly IToneGenerator toneGenerator; + private readonly int defaultDuration = 100; + private readonly int defaultPause = 50; + + /// + /// Initializes a new instance of the class + /// + /// The object to use for audio playback + public GameSounds(IToneGenerator toneGenerator) + { + this.toneGenerator = toneGenerator; + } + + /// + /// Plays the specified sound effect + /// + /// The sound effect to play + public Task PlayEffect(GameSoundEffect effect) + { + return effect switch + { + GameSoundEffect.Activation => PlayActivation(), + GameSoundEffect.Blip => PlayBlip(), + GameSoundEffect.BossBattle => PlayBossBattle(), + GameSoundEffect.ButtonPress => PlayButtonPress(), + GameSoundEffect.Coin => PlayCoin(), + GameSoundEffect.Collectible => PlayCollectible(), + GameSoundEffect.Countdown => PlayCountdown(), + GameSoundEffect.EnemyDeath => PlayEnemyDeath(), + GameSoundEffect.Explosion => PlayExplosion(), + GameSoundEffect.Footstep => PlayFootstep(), + GameSoundEffect.GameOver => PlayGameOver(), + GameSoundEffect.Health => PlayHealth(), + GameSoundEffect.Hit => PlayHit(), + GameSoundEffect.Jump => PlayJump(), + GameSoundEffect.Laser => PlayLaser(), + GameSoundEffect.LevelComplete => PlayLevelComplete(), + GameSoundEffect.MenuNavigate => PlayMenuNavigate(), + GameSoundEffect.PowerDown => PlayPowerDown(), + GameSoundEffect.PowerUp => PlayPowerUp(), + GameSoundEffect.SecretFound => PlaySecretFound(), + GameSoundEffect.Teleport => PlayTeleport(), + GameSoundEffect.Victory => PlayVictory(), + GameSoundEffect.Warning => PlayWarning(), + GameSoundEffect.WeaponSwitch => PlayWeaponSwitch(), + _ => throw new ArgumentException("Invalid game sound effect specified.", nameof(effect)), + }; + } + + + /// + /// Plays a simple blip sound effect + /// + private async Task PlayBlip() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration)); + } + + /// + /// Plays a power-up or item pick-up sound effect + /// + private async Task PlayPowerUp() + { + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(1760), TimeSpan.FromMilliseconds(defaultDuration)); + } + + /// + /// Plays a coin or currency collection sound effect + /// + private async Task PlayCoin() + { + await toneGenerator.PlayTone(new Frequency(1047), TimeSpan.FromMilliseconds(defaultDuration)); + } + + /// + /// Plays a jump or hop sound effect + /// + private async Task PlayJump() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration)); + } + + /// + /// Plays a hit or damage sound effect + /// + private async Task PlayHit() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(220), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a laser or projectile firing sound effect + /// + private async Task PlayLaser() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(1760), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays an explosion or destruction sound effect + /// + private async Task PlayExplosion() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(220), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(110), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(55), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a game over or failure sound effect + /// + private async Task PlayGameOver() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(220), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(110), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(55), TimeSpan.FromMilliseconds(defaultDuration)); + } + + /// + /// Plays a victory or success sound effect + /// + private async Task PlayVictory() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(659), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(1175), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays a countdown or timer sound effect + /// + private async Task PlayCountdown() + { + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(783.99), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(698.46), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(587.33), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(523.25), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a power-down sound effect + /// + private async Task PlayPowerDown() + { + await toneGenerator.PlayTone(new Frequency(523.25), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(349.23), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a button press sound effect + /// + private async Task PlayButtonPress() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration)); + } + + /// + /// Plays a menu navigation sound effect + /// + private async Task PlayMenuNavigate() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays a collectible item sound effect + /// + private async Task PlayCollectible() + { + await toneGenerator.PlayTone(new Frequency(1047), TimeSpan.FromMilliseconds(defaultDuration)); + } + + /// + /// Plays a boss battle theme. + /// + private async Task PlayBossBattle() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(554.37), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(659.25), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(783.99), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays a secret found sound effect + /// + private async Task PlaySecretFound() + { + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(1174.66), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(1396.91), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a level complete sound effect + /// + private async Task PlayLevelComplete() + { + await toneGenerator.PlayTone(new Frequency(1047), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(1396.91), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(1760), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(2217.46), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays a weapon switch sound effect + /// + private async Task PlayWeaponSwitch() + { + await toneGenerator.PlayTone(new Frequency(523.25), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(349.23), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays a warning or alarm sound effect + /// + private async Task PlayWarning() + { + await toneGenerator.PlayTone(new Frequency(1047), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(783.99), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a teleport or warp sound effect + /// + private async Task PlayTeleport() + { + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(783.99), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(659.25), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(523.25), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a health pickup or healing sound effect + /// + private async Task PlayHealth() + { + await toneGenerator.PlayTone(new Frequency(622.25), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(659.25), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(698.46), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(783.99), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays a footstep or movement sound effect + /// + private async Task PlayFootstep() + { + await toneGenerator.PlayTone(new Frequency(196), TimeSpan.FromMilliseconds(defaultDuration / 8)); + } + + /// + /// Plays an item activation or use sound effect + /// + private async Task PlayActivation() + { + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays an enemy death or defeat sound effect + /// + private async Task PlayEnemyDeath() + { + await toneGenerator.PlayTone(new Frequency(1568), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(1244.51), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(1046.5), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(783.99), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a splash sound effect + /// + public async Task PlaySplash() + { + await toneGenerator.PlayTone(new Frequency(220), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(440), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(220), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/MicroAudio.Enums.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/MicroAudio.Enums.cs new file mode 100644 index 0000000000..822cc76334 --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/MicroAudio.Enums.cs @@ -0,0 +1,294 @@ +namespace Meadow.Foundation.Audio +{ + /// + /// Represents a musical note + /// + public enum Pitch : int + { + /// + /// The C note + /// + C = 0, + /// + /// The C sharp note + /// + CSharp = 1, + /// + /// The enharmonic equivalent of C sharp + /// + DFlat = 1, + /// + /// The D note + /// + D = 2, + /// + /// The D sharp note + /// + DSharp = 3, + /// + /// The enharmonic equivalent of D sharp + /// + EFlat = 3, + /// + /// The E note + /// + E = 4, + /// + /// The F note + /// + F = 5, + /// + /// The F sharp note + /// + FSharp = 6, + /// + /// The enharmonic equivalent of F sharp + /// + GFlat = 6, + /// + /// The G note + /// + G = 7, + /// + /// The G sharp note + /// + GSharp = 8, + /// + /// The enharmonic equivalent of G sharp + /// + AFlat = 8, + /// + /// The A note + /// + A = 9, + /// + /// The A sharp note + /// + ASharp = 10, + /// + /// The enharmonic equivalent of A sharp + /// + BFlat = 10, + /// + /// The B note + /// + B = 11, + /// + /// Represents a rest note + /// + Rest + } + + /// + /// Represents the duration of a musical note + /// + public enum NoteDuration + { + /// + /// A whole note + /// + Whole = 4000, + /// + /// A half note + /// + Half = 2000, + /// + /// A quarter note + /// + Quarter = 1000, + /// + /// An eighth note + /// + Eighth = 500, + /// + /// A sixteenth note + /// + Sixteenth = 250, + /// + /// A thirty-second note + /// + ThirtySecond = 125, + /// + /// A whole note triplet + /// + WholeTriplet = 6000, + /// + /// A dotted half note + /// + DottedHalf = 3000 + } + + /// + /// Represents the different sound effects that can be played by the class + /// + public enum SystemSoundEffect + { + /// + /// An alarm or emergency sound effect + /// + Alarm, + /// + /// An alert or notification sound effect + /// + Alert, + /// + /// A simple beep sound effect + /// + Beep, + /// + /// A buzzing or vibrating sound effect + /// + Buzz, + /// + /// A chime or bell sound effect + /// + Chime, + /// + /// A short click sound effect + /// + Click, + /// + /// A failure or error sound effect + /// + Failure, + /// + /// A fanfare or celebratory sound effect + /// + Fanfare, + /// + /// A notification sound effect + /// + Notification, + /// + /// A popping sound effect + /// + Pop, + /// + /// A power-up sound effect + /// + PowerUp, + /// + /// A power-down sound effect + /// + PowerDown, + /// + /// A success or positive feedback sound effect + /// + Success, + /// + /// A short tick or click sound effect + /// + Tick, + /// + /// A warning or caution sound effect + /// + Warning, + } + + /// + /// Represents the different sound effects that can be played by the class + /// + public enum GameSoundEffect + { + /// + /// A sound effect indicating the activation or use of an item or power-up + /// + Activation, + /// + /// A simple blip sound effect + /// + Blip, + /// + /// A sound effect indicating a boss battle or end challenge + /// + BossBattle, + /// + /// A button press or selection sound effect + /// + ButtonPress, + /// + /// A coin or currency collection sound effect + /// + Coin, + /// + /// A sound effect indicating the collection of an item or bonus + /// + Collectible, + /// + /// A countdown or timer sound effect + /// + Countdown, + /// + /// A sound effect indicating the death or defeat of an enemy + /// + EnemyDeath, + /// + /// An explosion or destruction sound effect + /// + Explosion, + /// + /// A sound effect indicating a footstep or movement + /// + Footstep, + /// + /// A game over or failure sound effect + /// + GameOver, + /// + /// A sound effect indicating a health pickup or healing + /// + Health, + /// + /// A hit or damage sound effect + /// + Hit, + /// + /// A jump or hop sound effect + /// + Jump, + /// + /// A laser or projectile firing sound effect + /// + Laser, + /// + /// A sound effect indicating the completion of a level or challenge + /// + LevelComplete, + /// + /// A menu navigation or selection sound effect + /// + MenuNavigate, + /// + /// A power-up or item pick-up sound effect + /// + PowerUp, + /// + /// A power-down or failure sound effect + /// + PowerDown, + /// + /// A sound effect indicating the discovery of a secret or hidden item + /// + SecretFound, + /// + /// A sound effect indicating a spash in water + /// + Splash, + /// + /// A sound effect indicating a teleport or warp + /// + Teleport, + /// + /// A victory or success sound effect + /// + Victory, + /// + /// A warning or alarm sound effect + /// + Warning, + /// + /// A sound effect indicating a weapon or tool switch + /// + WeaponSwitch, + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/MicroAudio.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/MicroAudio.cs new file mode 100644 index 0000000000..8cf83490b5 --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/MicroAudio.cs @@ -0,0 +1,74 @@ +using Meadow.Peripherals.Speakers; +using System; +using System.Threading.Tasks; + +namespace Meadow.Foundation.Audio +{ + /// + /// Provide high level audio functions + /// + public partial class MicroAudio + { + readonly IToneGenerator speaker; + + SystemSounds systemSounds; + GameSounds gameSounds; + + /// + /// Create a new MicroAudio instance from a IToneGenerator driver instance + /// + /// An IToneGenerator object + public MicroAudio(IToneGenerator speaker) + { + this.speaker = speaker; + } + + /// + /// Set the playback volume + /// + /// The volume from 0-1 + public void SetVolume(float volume) + { + speaker?.SetVolume(volume); + } + + /// + /// Plays the specified system sound effect + /// + /// The sound effect to play + /// The number of times to play the sound effect + public async Task PlaySystemSound(SystemSoundEffect effect, int numberOfLoops = 1) + { + systemSounds ??= new SystemSounds(speaker); + + for(int i = 0; i < numberOfLoops; i++) + { + await systemSounds.PlayEffect(effect); + } + } + + /// + /// Plays the specified game sound effect + /// + /// The sound effect to play + /// /// The number of times to play the sound effect + public async Task PlayGameSound(GameSoundEffect effect, int numberOfLoops = 1) + { + gameSounds ??= new GameSounds(speaker); + + for (int i = 0; i < numberOfLoops; i++) + { + await gameSounds.PlayEffect(effect); + } + } + + /// + /// Play the specified song + /// + /// The song object + public Task PlaySong(Song song) + { + return song.Play(speaker); + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Note.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Note.cs new file mode 100644 index 0000000000..93b3ca8436 --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Note.cs @@ -0,0 +1,34 @@ +namespace Meadow.Foundation.Audio +{ + /// + /// Represents a musical note, with a specific pitch, octave, and duration + /// + public class Note + { + /// + /// The pitch of the note + /// + public Pitch Pitch { get; } + /// + /// The octave of the note + /// + public int Octave { get; } + /// + /// The duration of the note + /// + public NoteDuration Duration { get; } + + /// + /// Creates a new instance of the Note class, with the specified pitch, octave, and duration + /// + /// The pitch of the note + /// The octave of the note + /// The duration of the note + public Note(Pitch pitch, int octave, NoteDuration duration) + { + Pitch = pitch; + Octave = octave; + Duration = duration; + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/NotesToFrequency.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/NotesToFrequency.cs new file mode 100644 index 0000000000..24a2a7040e --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/NotesToFrequency.cs @@ -0,0 +1,35 @@ +using Meadow.Units; +using System; + +namespace Meadow.Foundation.Audio +{ + /// + /// A utility class for converting musical notes to their corresponding frequencies in hertz + /// + public class NotesToFrequency + { + /// + /// The frequency of the A4 note, in hertz + /// + public static Frequency A4Frequency { get; set; } = new Frequency(440.0, Frequency.UnitType.Hertz); + + private static double SemitoneRatio { get; } = 1.059463094359; + + /// + /// Converts the specified musical note to its frequency in hertz + /// + /// The musical note to convert + /// The frequency of the note in hertz + public static Frequency ConvertToFrequency(Note note) + { + int semitonesFromA4 = CalculateSemitonesFromA4(note.Pitch, note.Octave); + return A4Frequency * Math.Pow(SemitoneRatio, semitonesFromA4); + } + + private static int CalculateSemitonesFromA4(Pitch pitch, int octave) + { + int semitonesFromC0 = (int)pitch + (octave - 1) * 12; + return semitonesFromC0 - 9; + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Song.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Song.cs new file mode 100644 index 0000000000..d1e5abdcb1 --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/Song.cs @@ -0,0 +1,58 @@ +using Meadow.Peripherals.Speakers; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Meadow.Foundation.Audio +{ + /// + /// A class for playing a sequence of musical notes + /// + public class Song + { + /// + /// The collection of notes in order for the song + /// + public readonly List Notes = new List(); + + /// + /// Creates a new instance of the Song class + /// + public Song() + { + } + + /// + /// Adds a musical note to the sequence of notes to be played + /// + /// The musical note to add + public void AddNote(Note note) + { + Notes.Add(note); + } + + /// + /// Plays the sequence of musical notes, with the specified tempo + /// + /// The IToneGenerator object to play the song + /// The tempo of the music, in beats per minute + /// A Task representing the asynchronous playback operation + public async Task Play(IToneGenerator speaker, int tempo = 120) + { + foreach (var note in Notes) + { + int duration = (int)(60.0 / tempo * (int)note.Duration); + + if (note.Pitch == Pitch.Rest) + { + await Task.Delay(TimeSpan.FromMilliseconds(duration)); + } + else + { + var frequency = NotesToFrequency.ConvertToFrequency(note); + await speaker.PlayTone(frequency, TimeSpan.FromMilliseconds(duration)); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/SystemSounds.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/SystemSounds.cs new file mode 100644 index 0000000000..cc4736b9a9 --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Driver/SystemSounds.cs @@ -0,0 +1,198 @@ +using Meadow.Peripherals.Speakers; +using Meadow.Units; +using System; +using System.Threading.Tasks; + +namespace Meadow.Foundation.Audio +{ + /// + /// A class for playing system sounds using an IToneGenerator + /// + public class SystemSounds + { + private readonly int defaultDuration; + private readonly int defaultPause; + private readonly IToneGenerator toneGenerator; + + /// + /// Creates a new instance of the SystemSounds class, using the specified IToneGenerator for audio output + /// + /// The IToneGenerator to use for audio output + /// The default duration of system sounds, in milliseconds (optional; defaults to 100) + /// The default pause between system sounds, in milliseconds (optional; defaults to 50) + + public SystemSounds(IToneGenerator toneGenerator, int defaultDuration = 100, int defaultPause = 50) + { + this.toneGenerator = toneGenerator; + this.defaultDuration = defaultDuration; + this.defaultPause = defaultPause; + } + + /// + /// Plays the specified sound effect + /// + /// The sound effect to play + public Task PlayEffect(SystemSoundEffect effect) + { + return effect switch + { + SystemSoundEffect.Alert => PlayAlert(), + SystemSoundEffect.Alarm => PlayAlarm(), + SystemSoundEffect.Beep => PlayBeep(), + SystemSoundEffect.Buzz => PlayBuzz(), + SystemSoundEffect.Chime => PlayChime(), + SystemSoundEffect.Click => PlayClick(), + SystemSoundEffect.Failure => PlayFailure(), + SystemSoundEffect.Fanfare => PlayFanfare(), + SystemSoundEffect.Notification => PlayNotification(), + SystemSoundEffect.Pop => PlayPop(), + SystemSoundEffect.PowerDown => PlayPowerDown(), + SystemSoundEffect.PowerUp => PlayPowerUp(), + SystemSoundEffect.Success => PlaySuccess(), + SystemSoundEffect.Tick => PlayTick(), + SystemSoundEffect.Warning => PlayWarning(), + _ => throw new ArgumentException($"Unknown effect: {effect}"), + }; + } + + + private async Task PlayBeep() + { + await toneGenerator.PlayTone(new Frequency(1000), TimeSpan.FromMilliseconds(defaultDuration)); + } + + private async Task PlaySuccess() + { + await toneGenerator.PlayTone(new Frequency(1000), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(1500), TimeSpan.FromMilliseconds(defaultDuration)); + } + + private async Task PlayFailure() + { + await toneGenerator.PlayTone(new Frequency(1500), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(1000), TimeSpan.FromMilliseconds(defaultDuration)); + } + + private async Task PlayWarning() + { + await toneGenerator.PlayTone(new Frequency(500), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(500), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(500), TimeSpan.FromMilliseconds(defaultDuration)); + } + + private async Task PlayAlarm() + { + for (int i = 0; i < 5; i++) + { + await toneGenerator.PlayTone(new Frequency(1000), TimeSpan.FromMilliseconds(defaultDuration)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + } + } + + private async Task PlayTick() + { + await toneGenerator.PlayTone(new Frequency(1000), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + private async Task PlayChime() + { + await toneGenerator.PlayTone(new Frequency(262), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(330), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(392), TimeSpan.FromMilliseconds(defaultDuration / 4)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(523), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays a buzzing or vibrating sound effect + /// + private async Task PlayBuzz() + { + await toneGenerator.PlayTone(new Frequency(500), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(500), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a fanfare or celebratory sound effect + /// + private async Task PlayFanfare() + { + await toneGenerator.PlayTone(new Frequency(784), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(659), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(523), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(784), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(659), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(523), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays an alert or notification sound effect + /// + private async Task PlayAlert() + { + await toneGenerator.PlayTone(new Frequency(1047), TimeSpan.FromMilliseconds(defaultDuration / 8)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(1175), TimeSpan.FromMilliseconds(defaultDuration / 8)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(1319), TimeSpan.FromMilliseconds(defaultDuration / 8)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(1397), TimeSpan.FromMilliseconds(defaultDuration / 8)); + } + + /// + /// Plays a short click sound effect + /// + private async Task PlayClick() + { + await toneGenerator.PlayTone(new Frequency(1000), TimeSpan.FromMilliseconds(defaultDuration / 8)); + } + + /// + /// Plays a popping sound effect + /// + private async Task PlayPop() + { + await toneGenerator.PlayTone(new Frequency(500), TimeSpan.FromMilliseconds(defaultDuration / 4)); + } + + /// + /// Plays a power-up sound effect + /// + private async Task PlayPowerUp() + { + await toneGenerator.PlayTone(new Frequency(200), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(1000), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a power-down sound effect + /// + private async Task PlayPowerDown() + { + await toneGenerator.PlayTone(new Frequency(1000), TimeSpan.FromMilliseconds(defaultDuration / 2)); + await toneGenerator.PlayTone(new Frequency(200), TimeSpan.FromMilliseconds(defaultDuration / 2)); + } + + /// + /// Plays a notification sound effect + /// + private async Task PlayNotification() + { + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 8)); + await toneGenerator.PlayTone(new Frequency(784), TimeSpan.FromMilliseconds(defaultDuration / 8)); + await toneGenerator.PlayTone(new Frequency(698), TimeSpan.FromMilliseconds(defaultDuration / 8)); + await Task.Delay(TimeSpan.FromMilliseconds(defaultPause)); + await toneGenerator.PlayTone(new Frequency(880), TimeSpan.FromMilliseconds(defaultDuration / 8)); + await toneGenerator.PlayTone(new Frequency(784), TimeSpan.FromMilliseconds(defaultDuration / 8)); + await toneGenerator.PlayTone(new Frequency(698), TimeSpan.FromMilliseconds(defaultDuration / 8)); + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/Audio.MicroAudio_Sample.csproj b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/Audio.MicroAudio_Sample.csproj new file mode 100644 index 0000000000..0f5262f559 --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/Audio.MicroAudio_Sample.csproj @@ -0,0 +1,20 @@ + + + https://github.com/WildernessLabs/Meadow.Foundation + Wilderness Labs, Inc + Wilderness Labs, Inc + true + netstandard2.1 + Library + App + + + + + + + + Always + + + diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/CScale.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/CScale.cs new file mode 100644 index 0000000000..98acd8337f --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/CScale.cs @@ -0,0 +1,24 @@ +using Meadow.Foundation.Audio; + +namespace Audio.MicroAudio_Sample +{ + namespace SongPlayer + { + internal class CScale : Song + { + public CScale() + { + AddNotes(); + } + + void AddNotes() + { + for (int i = 0; i < 12; i++) + { + AddNote(new Note((Pitch)(i), 3, NoteDuration.Quarter)); + } + AddNote(new Note(Pitch.C, 4, NoteDuration.Quarter)); + } + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/MeadowApp.cs new file mode 100644 index 0000000000..edd889e0a0 --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/MeadowApp.cs @@ -0,0 +1,86 @@ +using Audio.MicroAudio_Sample.SongPlayer; +using Meadow; +using Meadow.Devices; +using Meadow.Foundation.Audio; +using Meadow.Gateways.Bluetooth; +using Meadow.Peripherals.Speakers; +using System; +using System.Threading.Tasks; + +namespace MicroAudio_Sample +{ + public class MeadowApp : App + { + private MicroAudio audio; + + IToneGenerator speaker; + + public override Task Initialize() + { + Resolver.Log.Info("Initialize..."); + + speaker = new PiezoSpeaker(Device.Pins.D11); + + audio = new MicroAudio(speaker); + + return Task.CompletedTask; + } + + public override async Task Run() + { + Resolver.Log.Info("Play happy birthday"); + await HappyBirthDay(speaker); + + await Task.Delay(1000); + + Resolver.Log.Info("Play C scale"); + var scale = new CScale(); + await audio.PlaySong(scale); + + await Task.Delay(1000); + + Resolver.Log.Info("Sound effects test"); + await SoundEffectsTest(); + + Resolver.Log.Info("Game effects test"); + await GameEffectsTest(); + } + + Task HappyBirthDay(IToneGenerator speaker) + { + var happyBirthday = new Song(); + happyBirthday.AddNote(new Note(Pitch.C, 3, NoteDuration.Quarter)); + happyBirthday.AddNote(new Note(Pitch.C, 3, NoteDuration.Quarter)); + happyBirthday.AddNote(new Note(Pitch.D, 3, NoteDuration.Half)); + happyBirthday.AddNote(new Note(Pitch.C, 3, NoteDuration.Half)); + happyBirthday.AddNote(new Note(Pitch.F, 3, NoteDuration.Half)); + happyBirthday.AddNote(new Note(Pitch.E, 3, NoteDuration.Whole)); + + return happyBirthday.Play(speaker, 160); + } + + async Task GameEffectsTest() + { + foreach (GameSoundEffect effect in Enum.GetValues(typeof(GameSoundEffect))) + { + Resolver.Log.Info($"Playing {effect} game effect..."); + await audio.PlayGameSound(effect); + await Task.Delay(1000); + } + + Resolver.Log.Info("Sound effects demo complete."); + } + + async Task SoundEffectsTest() + { + foreach (SystemSoundEffect effect in Enum.GetValues(typeof(SystemSoundEffect))) + { + Resolver.Log.Info($"Playing {effect} sound effect..."); + await audio.PlaySystemSound(effect); + await Task.Delay(1000); + } + + Resolver.Log.Info("Sound effects demo complete."); + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/meadow.config.yaml b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/meadow.config.yaml new file mode 100644 index 0000000000..32363cb69c --- /dev/null +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Audio.MicroAudio/Samples/Audio.MicroAudio_Sample/meadow.config.yaml @@ -0,0 +1,2 @@ +MonoControl: + Options: --jit \ No newline at end of file diff --git a/Source/Meadow.Foundation.sln b/Source/Meadow.Foundation.sln index 564639055e..0e85ddfe25 100644 --- a/Source/Meadow.Foundation.sln +++ b/Source/Meadow.Foundation.sln @@ -1227,6 +1227,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mcp9600_Sample", "Meadow.Fo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mcp9601_Sample", "Meadow.Foundation.Peripherals\Sensors.Temperature.Mcp960x\Samples\Mcp9601_Sample\Mcp9601_Sample.csproj", "{41A4E1A7-0BC4-474F-910F-F140EEA1A4BE}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Audio.MicroAudio", "Audio.MicroAudio", "{0236BB4E-B417-4549-B69E-79AC81FC3056}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Audio.MicroAudio", "Meadow.Foundation.Libraries_and_Frameworks\Audio.MicroAudio\Driver\Audio.MicroAudio.csproj", "{F3F3966C-E416-41EF-889B-518FFECFC780}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{99AABDF6-1EE4-4099-B61C-E49CA6F5CA0F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Audio.MicroAudio_Sample", "Meadow.Foundation.Libraries_and_Frameworks\Audio.MicroAudio\Samples\Audio.MicroAudio_Sample\Audio.MicroAudio_Sample.csproj", "{CDC8B582-C7F8-4578-8F1A-0B4B817D2632}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -2917,6 +2925,18 @@ Global {41A4E1A7-0BC4-474F-910F-F140EEA1A4BE}.Release|Any CPU.ActiveCfg = Release|Any CPU {41A4E1A7-0BC4-474F-910F-F140EEA1A4BE}.Release|Any CPU.Build.0 = Release|Any CPU {41A4E1A7-0BC4-474F-910F-F140EEA1A4BE}.Release|Any CPU.Deploy.0 = Release|Any CPU + {F3F3966C-E416-41EF-889B-518FFECFC780}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3F3966C-E416-41EF-889B-518FFECFC780}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3F3966C-E416-41EF-889B-518FFECFC780}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {F3F3966C-E416-41EF-889B-518FFECFC780}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3F3966C-E416-41EF-889B-518FFECFC780}.Release|Any CPU.Build.0 = Release|Any CPU + {F3F3966C-E416-41EF-889B-518FFECFC780}.Release|Any CPU.Deploy.0 = Release|Any CPU + {CDC8B582-C7F8-4578-8F1A-0B4B817D2632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDC8B582-C7F8-4578-8F1A-0B4B817D2632}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDC8B582-C7F8-4578-8F1A-0B4B817D2632}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {CDC8B582-C7F8-4578-8F1A-0B4B817D2632}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDC8B582-C7F8-4578-8F1A-0B4B817D2632}.Release|Any CPU.Build.0 = Release|Any CPU + {CDC8B582-C7F8-4578-8F1A-0B4B817D2632}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3529,6 +3549,10 @@ Global {754AFC87-A98F-4BD6-A934-61DB69C0B643} = {CBF95E20-6021-4E5B-807A-E099DBC98764} {3AC34973-19FC-453A-A90E-4406EB355F72} = {573A2A08-CB36-480E-9C5D-56F851847045} {41A4E1A7-0BC4-474F-910F-F140EEA1A4BE} = {573A2A08-CB36-480E-9C5D-56F851847045} + {0236BB4E-B417-4549-B69E-79AC81FC3056} = {E0384B86-37FC-403C-B1F7-AA5D1B869EB1} + {F3F3966C-E416-41EF-889B-518FFECFC780} = {0236BB4E-B417-4549-B69E-79AC81FC3056} + {99AABDF6-1EE4-4099-B61C-E49CA6F5CA0F} = {0236BB4E-B417-4549-B69E-79AC81FC3056} + {CDC8B582-C7F8-4578-8F1A-0B4B817D2632} = {99AABDF6-1EE4-4099-B61C-E49CA6F5CA0F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AF7CA16F-8C38-4546-87A2-5DAAF58A1520}