Music with a Piezo Speaker - Project Showcase - Netduino Forums
   
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.
Photo

Music with a Piezo Speaker


  • Please log in to reply
7 replies to this topic

#1 Ray Mckaig

Ray Mckaig

    New Member

  • Members
  • Pip
  • 1 posts

Posted 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.

#2 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7767 posts
  • LocationNew York, NY

Posted 13 December 2010 - 09:20 PM

That's pretty cool! A lot nicer than the frequency generator on my old HP 48G calculator :) Now to go find that Super Mario soundtrack... Chris

#3 Charles

Charles

    Advanced Member

  • Members
  • PipPipPip
  • 192 posts

Posted 28 December 2010 - 01:22 AM

I have been waiting a few days for just the right opportunity for my 100th post.... I'm happy to say I have found it! I would like to congratulate you on the creation of this code! It is well laid out, commented, and classful. It also demonstrates no only a knowledge of programming, but of music theory, a fairly rare combination! Well done! P.S. - I noticed this to be your first post. Allow me to formally welcome you to the community!

#4 Omar (OZ)

Omar (OZ)

    Advanced Member

  • Members
  • PipPipPip
  • 564 posts

Posted 28 December 2010 - 01:28 AM

Thats.... awesome. I made something like this before, but yours is WAY better ^_^ well done!

#5 octoberclub

octoberclub

    New Member

  • Members
  • Pip
  • 2 posts
  • LocationBrighton, UK

Posted 03 February 2011 - 11:41 AM

I think this is a lovely example of a small project you can do with the netduino without too much electonics knowledge. I showed it to a small group at a show and tell session this week and it went down well. So thanks. Would you have a link to the full code anywhere? Not all the code is supplied in this thread so, to get it working, I had to implement/guess some of the classes. I think it would be nice to have the examples in this forum up on github to download and play with. I'd be happy to do it, but obviously with your permission first. Also - I really like the FloatDictionary but I wonder if you could just calculate the frequency of notes from a reference as described here? http://www.techlib.c...frequencies.htm

#6 Rick Mogstad

Rick Mogstad

    New Member

  • Members
  • Pip
  • 1 posts

Posted 03 November 2011 - 03:04 AM

I decided to play with this, and there were a few things that needed cleaned up, and I put the PiezoSpeaker piece in it's own library. I have attached here with the minor modifications I did, but credit definitely goes to Ray.

Attached Files



#7 Giuliano

Giuliano

    Advanced Member

  • Members
  • PipPipPip
  • 361 posts
  • LocationSimi Valley, CA

Posted 31 May 2012 - 01:17 AM

Awesome project, I am going to play with it tonight. :)

I decided to play with this, and there were a few things that needed cleaned up, and I put the PiezoSpeaker piece in it's own library. I have attached here with the minor modifications I did, but credit definitely goes to Ray.



#8 octoberclub

octoberclub

    New Member

  • Members
  • Pip
  • 2 posts
  • LocationBrighton, UK

Posted 22 October 2012 - 01:08 PM

Its been a while and I forgot to update this post with the change I'd made to calculate the frequency.

Finally written it up here: http://octoberclub.w...-making-sounds/

The fully working project is on github:

https://github.com/o...uino.PiezoTest2

and here's the modified code to calculate frequencies:


using System;
using Microsoft.SPOT;

public static class NoteFrequencies
{
    /// <summary>
    /// Static method to calculate the frequency of a note from its name and octave.
    /// </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 CalculateFrequency(int octave, string note)
    {
        string noteLC = note.ToLower();
        string[] notes = "c,c#,d,d#,e,f,f#,g,g#,a,a#,b".Split(',');

        // loop through each note until we find the index of the one we want        
        for (int n = 0; n < notes.Length; n++)
        {
            if (notes[n] == noteLC // frequency found for major and sharp notes
                || (note.Length > 1 && noteLC[1] == 'b' && notes[n + 1][0] == noteLC[0])) // or flat of next note
            {
                // Multiply initial note by 2 to the power (n / 12) to get correct frequency, 
                //  (where n is the number of notes above the first note). 
                //  Then mutiply that value by 2 to go up each octave
                return (16.35f * (float)System.Math.Pow(2, (n / 12)))
                    * (float)System.Math.Pow(2, octave);
            }
        }
        throw new ArgumentException("No frequency found for note : " + note, note);
    }
}






0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

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.