Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.

Ray Mckaig

Member Since 27 Nov 2010
Offline Last Active Jan 21 2011 07:22 PM
-----

#6044 Music with a Piezo Speaker

Posted by Ray Mckaig on 13 December 2010 - 09:14 PM

I thought I'd share with you a little project I put together demonstrating using a Piezo Buzzer/Speaker to produce music.

I use "music" here in the loosest terms as only one note can be played at once, but hey, if you had two or more of these little fellas, it could get interesting.

Okay, playing a noise on the speaker is fairly simple, but I wanted to be a tad more sophisticated. I wanted to be able to type letters into my program and have them play without me having to bother with finding out the frequencies of the individual notes.

There's a wee video on YouTube of me demoing this project.

There's quite a bit of code here, and I've tried to comment it where explanation was needed, but feel free to ask questions, comment and tear apart the code. I'm learning how to use this fantastic Netduino board and I've got a long way to go. So here goes... big deep breath...

The first class is required because the Micro Framework does not support Generics, and various standard .NET classes such as Dictionary. Note frequencies can be non integer, so I put together a dictionary class that stores a float value against a string key.

    using LED.ExtensionMethods;

    /// <summary>
    /// Micro Framework does not contain a dictionary object (or generics!!!)
    /// This is a basic dictionary that holds float values against a string key.
    /// </summary>
    public class FloatDictionary
    {
        private string[] _keys;
        private float[] _values;
        private int _count = 0;

        private int _initialSize;
        private int _growBy;

        public FloatDictionary(int initialSize, int growBy)
        {
            _initialSize = initialSize;
            _growBy = growBy;

            _keys = new string[initialSize];
            _values = new float[initialSize];
        }

        /// <summary>
        /// Add a value to the dictionary. If the key already
        /// exists, the new value is ignored
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public FloatDictionary Add(string key, float value)
        {
            if (_keys.IndexOf(key) == -1)
            {
                _keys[_count] = key;
                _values[Count] = value;

                _count++;

                // There's probably a better way to do this but for now
                // if we reach the current capacity of the arrays, we need
                // to create new arrays with a new larger size and copy
                // the contents of the old arrays to the new.
                if (_count == _keys.Length)
                {
                    string[] newKeys = new string[_count + _growBy];
                    float[] newValues = new float[_count + _growBy];

                    for (int n = 0; n < _count; n++)
                    {
                        newKeys[n] = _keys[n];
                        newValues[n] = _values[n];
                    }

                    _keys = newKeys;
                    _values = newValues;
                }
            }

            return this;
        }

        /// <summary>
        /// Check to see if dictionary contains the key
        /// </summary>
        /// <param name="key">String key</param>
        /// <returns>Boolean</returns>
        public bool ContainsKey(string key)
        {
            return Array.IndexOf(_keys, key) != -1;
        }

        /// <summary>
        ///  Returns the key for the item at position "position"
        ///  If the position is outside the array limits, null is returned
        /// </summary>
        /// <param name="position"></param>
        /// <returns></returns>
        public string GetKey(int position)
        {
            if (position < 0 || position >= _count)
                return null;
            else
                return _keys[position];
        }

        /// <summary>
        /// Return the value for the supplied key
        /// If the key doesn't exist, return null
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object GetValue(string key)
        {
            int position = Array.IndexOf(_keys, key);

            if (position == -1)
                return null;
            else
                return _values[position];
        }

        public int Count
        {
            get { return _count; }
        }
    }

Next is a class that exposes a static array of FloatDictionary objects that store the frequency information for seven octaves of notes.

public class NoteFrequencies
    {
        public static FloatDictionary[] Octaves = new FloatDictionary[] 
        {
            new FloatDictionary(17,10)      // C0
                .Add("c", 16.35f)
                .Add("c#", 17.32f)
                .Add("db", 17.32f)
                .Add("d", 18.35f)
                .Add("d#", 19.45f)
                .Add("eb", 19.45f)
                .Add("e", 20.6f)
                .Add("f", 21.83f)
                .Add("f#", 23.12f)
                .Add("gb", 23.12f)
                .Add("g", 24.5f)
                .Add("g#", 25.96f)
                .Add("ab", 25.96f)
                .Add("a", 27.5f)
                .Add("a#", 29.14f)
                .Add("bb", 29.14f)
                .Add("b", 30.87f),

            new FloatDictionary(17,10)      // C1
                .Add("c", 32.7f)
                .Add("c#", 34.65f)
                .Add("db", 34.65f)
                .Add("d", 36.71f)
                .Add("d#", 38.89f)
                .Add("eb", 38.89f)
                .Add("e", 41.2f)
                .Add("f", 43.65f)
                .Add("f#", 46.25f)
                .Add("gb", 46.25f)
                .Add("g", 49.0f)
                .Add("g#", 51.91f)
                .Add("ab", 51.91f)
                .Add("a", 55.0f)
                .Add("a#", 58.27f)
                .Add("bb", 58.27f)
                .Add("b", 61.74f),
                
            new FloatDictionary(17,10)      // C2
                .Add("c", 65.41f)
                .Add("c#", 69.3f)
                .Add("db", 69.3f)
                .Add("d", 73.42f)
                .Add("d#", 77.78f)
                .Add("eb", 77.78f)
                .Add("e", 82.41f)
                .Add("f", 87.31f)
                .Add("f#", 92.5f)
                .Add("gb", 92.5f)
                .Add("g", 98.0f)
                .Add("g#", 103.83f)
                .Add("ab", 103.83f)
                .Add("a", 110.0f)
                .Add("a#", 116.54f)
                .Add("bb", 116.54f)
                .Add("b", 123.47f),

            new FloatDictionary(17,10)      // C3
                .Add("c", 130.81f)
                .Add("c#", 138.59f)
                .Add("db", 138.59f)
                .Add("d", 146.83f)
                .Add("d#", 155.56f)
                .Add("eb", 155.56f)
                .Add("e", 164.81f)
                .Add("f", 174.61f)
                .Add("f#", 185.0f)
                .Add("gb", 185.0f)
                .Add("g", 196.0f)
                .Add("g#", 207.65f)
                .Add("ab", 207.65f)
                .Add("a", 220.0f)
                .Add("a#", 233.08f)
                .Add("bb", 233.08f)
                .Add("b", 246.94f),
                
            new FloatDictionary(17,10)      // C4
                .Add("c", 261.63f)
                .Add("c#", 277.18f)
                .Add("db", 277.18f)
                .Add("d", 293.66f)
                .Add("d#", 311.13f)
                .Add("eb", 311.13f)
                .Add("e", 329.63f)
                .Add("f", 349.23f)
                .Add("f#", 369.99f)
                .Add("gb", 369.99f)
                .Add("g", 392.00f)
                .Add("g#", 415.30f)
                .Add("ab", 415.30f)
                .Add("a", 440.00f)
                .Add("a#", 466.16f)
                .Add("bb", 466.16f)
                .Add("b", 493.88f),

            new FloatDictionary(17,10)      // C5
                .Add("c", 523.25f)
                .Add("c#", 554.37f)
                .Add("db", 554.37f)
                .Add("d", 587.33f)
                .Add("d#", 622.25f)
                .Add("eb", 622.25f)
                .Add("e", 659.26f)
                .Add("f", 698.46f)
                .Add("f#", 739.99f)
                .Add("gb", 739.99f)
                .Add("g", 783.99f)
                .Add("g#", 830.61f)
                .Add("ab", 830.61f)
                .Add("a", 880.0f)
                .Add("a#", 932.33f)
                .Add("bb", 932.33f)
                .Add("b", 987.77f),

            new FloatDictionary(17,10)      // C6
                .Add("c", 1046.5f)
                .Add("c#", 1108.73f)
                .Add("db", 1108.73f)
                .Add("d", 1174.66f)
                .Add("d#", 1244.51f)
                .Add("eb", 1244.51f)
                .Add("e", 1318.51f)
                .Add("f", 1396.91f)
                .Add("f#", 1474.98f)
                .Add("gb", 1474.98f)
                .Add("g", 1567.98f)
                .Add("g#", 1661.22f)
                .Add("ab", 1661.22f)
                .Add("a", 1760.0f)
                .Add("a#", 1864.66f)
                .Add("bb", 1864.66f)
                .Add("b", 1975.53f),

            new FloatDictionary(17,10)      // C7
                .Add("c", 2093.0f)
                .Add("c#", 2217.46f)
                .Add("db", 2217.46f)
                .Add("d", 2349.32f)
                .Add("d#", 2849.02f)
                .Add("eb", 2849.02f)
                .Add("e", 2637.02f)
                .Add("f", 2793.83f)
                .Add("f#", 2959.96f)
                .Add("gb", 2959.96f)
                .Add("g", 3135.96f)
                .Add("g#", 3322.44f)
                .Add("ab", 3322.44f)
                .Add("a", 3520.0f)
                .Add("a#", 3729.31f)
                .Add("bb", 3729.31f)
                .Add("b", 3951.07f)
        };
            
        /// <summary>
        /// Static method to locate a note in the dictionaries defined above
        /// </summary>
        /// <param name="octave">Octave number (0-7)</param>
        /// <param name="note">Note to be found: eg. F#, A, Bb</param>
        /// <returns></returns>
        public static float GetFrequency(int octave, string note) 
        {
            float frequency = 0.0f;

            if (octave >= 0 && octave <= 7)
            {
                if (Octaves[octave].ContainsKey(note.ToLower()))
                {
                    frequency = (float)Octaves[octave].GetValue(note.ToLower());
                }
            }

            return frequency;
        }
    }

This next class defines how we interface with the Piezo speaker.

    using SecretLabs.NETMF.Hardware;
    using System.Threading;

    public class PiezoSpeaker
    {
        private PWM _pin;

        // if the _busy flag is true we will just
        // ignore any request to make noise.
        private bool _busy = false;
        
        public PiezoSpeaker(Cpu.Pin pin)
        {
            _pin = new PWM(pin);

            // take the pin low, so the speaker
            // doesn't make any noise until we
            // ask it to
            _pin.SetDutyCycle(0);
        }

        /// <summary>
        /// Play a particular frequency for a defined
        /// time period
        /// </summary>
        /// <param name="frequency">The frequency (in hertz) of the note to be played</param>
        /// <param name="duration">How long (in milliseconds: 1000 = 1 second) the note is to play for</param>
        public void Play(float frequency, int duration)
        {
            if (!_busy)
            {
                _busy = true;

                // calculate the actual period and turn the
                // speaker on for the defined period of time
                uint period = (uint)(1000000 / frequency);
                _pin.SetPulse(period, period / 2);

                Thread.Sleep(duration);
                
                // turn the speaker off
                _pin.SetDutyCycle(0);
                _busy = false;
            }
        }

        /// <summary>
        /// Play an array of Notes
        /// </summary>
        /// <param name="notes">An array of Note objects, each defining a frequency and duration</param>
        public void Play(Note[] notes)
        {
            foreach (Note note in notes)
            {
                Play(note.Frequency, note.Duration);
            }
        }

        /// <summary>
        /// Play a sequence of notes using standard letters C, D, E F#, etc. There
        /// are 7 octaves defined and the following instructions allow the whole
        /// range of notes to be accessed.
        /// 
        /// Notes: C, C#, Db, D, D#, Eb, E, F, F#, Gb, G, G#, Ab, A, A#, Bb, B
        /// Next note will be from the octave above: +
        /// Next note will be from the octave below: -
        /// Double length of next note: *
        /// Halve length of the next note: /
        /// 
        /// Example: Auld Lang Syne
        /// C * F / F * F A G / F * G A F / F A + C D
        /// 
        /// TODO
        /// Would be useful to provide a means of defining the tempo of the next note
        /// rather than the simplistic double and half.
        /// 
        /// Maybe use numbers to define the length of notes
        /// 1/4, 1/8, 1/16, 2/3, etc.
        /// </summary>
        /// <param name="sequence">A string of notes speperated by a space</param>
        /// <param name="initialOctave">The number of the default octave (4 = middle C), Allowed values: 0-7</param>
        /// <param name="initialTempo">The length of the first note specified in milliseconds</param>
        public void Play(string sequence, int initialOctave, int initialTempo)
        {
            string[] notes = sequence.Split(' ');

            // initialise the Note object we'll reuse for playing each note
            Note note = new Note(initialOctave, "c", initialTempo);

            foreach (string item in notes)
            {
                if (item != "")
                {
                    if (item == "*") // double the current tempo
                    {
                        note.Duration *= 2;
                    }
                    else if (item == "/") // halve the current tempo
                    {
                        note.Duration /= 2;
                    }
                    else if (item == "-") // move to the octave below
                    {
                        if (note.Octave > 0)
                            note.Octave--;
                    }
                    else if (item == "+") // move to the octave above
                    {
                        if (note.Octave < 8)
                            note.Octave++;
                    }
                    else
                    {
                        note.NoteLetter = item;
                        Play(note.Frequency, note.Duration);

                        // small gap between notes
                        Thread.Sleep(10);
                    }
                }
            }
        }
    }

Next is a little class that defines a Note. Now a note is really just a frequency, but we can create it using a letter such as F or C# or Bb.

    public class Note
    {

        private int _octave;
        private string _noteLetter;
        private float _frequency;
        private int _duration;

        public Note(float frequency, int duration)
        {
            _noteLetter = "";
            _octave = 4;
            _frequency = frequency;
            _duration = duration;
        }

        public Note(int octave, string note, int duration)
        {
            SetFrequency(octave, note);

            _duration = duration;
        }

        /// <summary>
        /// Sets the note based on a text note eg. F#
        /// and the octave number.
        /// </summary>
        /// <param name="octave">Octave note is to be played from (0-7)</param>
        /// <param name="note">String note. eg. C or F# or Bb</param>
        private void SetFrequency(int octave, string note) 
        {
            // get the frequency
            float frequency = NoteFrequencies.GetFrequency(octave, note);

            // if the note was valid
            if (frequency != 0.0f)
            {
                _frequency = frequency;
                _octave = octave;
                _noteLetter = note;
            }
            else // otherwise default to middle C
            {
                _frequency = NoteFrequencies.GetFrequency(4, "c");
                _octave = 4;
                _noteLetter = "c";
            }
        }

        public void Play(PiezoSpeaker speaker) 
        {
            speaker.Play(_frequency, _duration);
        }

        public string NoteLetter
        {
            get { return _noteLetter; }
            set
            {
                SetFrequency(_octave, value);
            }
        }

        public int Octave
        {
            get { return _octave; }
            set 
            { 
                _octave = value; 


            }
        }

        public float Frequency
        {
            get { return _frequency; }
        }

        public int Duration
        {
            get { return _duration; }
            set { _duration = value; }
        }
    }

And finally, some test code that demonstrates using the above code to play music.

        public static void Test_PiezoSpeaker()
        {
            // setup the speaker on digital pin 5
            PiezoSpeaker speaker = new PiezoSpeaker(Pins.GPIO_PIN_D5);

            // define some tunes
            string[] tunes = new string[] 
            {
                "C * F / F * F A G / F * G A F / F A + C D" , // Auld Lang Syne
                "D / D * * E D G * F# / / D / D * * E D A * G / / D / D * * + D - B G F# * E / / + C / C * * - B G A * G", // Happy birthday
                "C C G G A A * G / F F E E D D * C / G G F F E E * D / G G F F E E * D / C C G G A A * G / F F E E D D * C" , // twinkle, twinkle
                "A B + C D E F# G# A G F E D C - B A" // melodic minor scale
            };


            // each time the button is pressed we're going to cycle to the next tune in the list
            // if we reach the end of the list, start at the beginning again.

            int which = -1;

            Button button = new Button(Pins.ONBOARD_SW1);
            button.OnPressed += delegate
            {
                which++;

                if (which == tunes.Length)
                    which = 0;

                speaker.Play(tunes[which], 4, 333);
            };

            // as this is driven by the button being pressed, we don't need to poll
            // anything so we just put the main thread to sleep and wait... and wait... and wait...

            Thread.Sleep(Timeout.Infinite);
        }

Any questions, feel free to ask, and of course tear the code apart, advise of me of better ways to do things.

Enjoy.


home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.