Tuesday, 8 July 2014

Plucking some Guitar with Silverlight

Following on from the improvements to the basic KS algorithm covered in my previous two posts to address the fine-tuning this only really makes sense listening to something more representative, so I wanted to add in some code that would easily allow putting together some basic guitar patterns.

Firstly, building on the earlier simple Theory class which contained the various different pitch frequencies I've made a very simple Fretboard class that returns the correct pitch mapping for a traditionally tuned (EADGBE) 6-string guitar:

    public class Fretboard
    {
        static double[,] fretboard = new double[6, 16] 
        {
            //1
            { 
                Theory.E4, Theory.F4, Theory.Fs4, Theory.G4, Theory.Gs4, Theory.A4, Theory.As4, Theory.B5, Theory.C5,
                Theory.Cs5, Theory.D5, Theory.Ds5, Theory.E5, Theory.F5, Theory.Fs5, Theory.G5
            
            },

            //2
            { 
                Theory.B3, Theory.C4, Theory.Cs4, Theory.D4, Theory.Ds4, Theory.E4, Theory.F4, Theory.Fs4, Theory.G4 ,
                Theory.Gs4, Theory.A4, Theory.As4, Theory.B4, Theory.C5, Theory.Cs5, Theory.D5
            },

            //3
            { 
                Theory.G3, Theory.Gs3, Theory.A3, Theory.As3, Theory.B3, Theory.C4, Theory.Cs4, Theory.D4, Theory.Ds4,
                Theory.E4, Theory.F4, Theory.Fs4, Theory.G4, Theory.Gs4, Theory.A4, Theory.As4
            },
            
            //4
            { 
                Theory.D3, Theory.Ds3, Theory.E3, Theory.F3, Theory.Fs3, Theory.G3, Theory.Gs3, Theory.A3, Theory.As3,
                Theory.B3, Theory.C4, Theory.Cs4, Theory.D4, Theory.Ds4, Theory.E4, Theory.F4
            },
            
            //5
            { 
                Theory.A2, Theory.As2, Theory.B2, Theory.C3, Theory.Cs3, Theory.D4, Theory.Ds4, Theory.E4, Theory.F4,
                Theory.Fs4, Theory.G4, Theory.Gs4, Theory.A4, Theory.As4, Theory.B4, Theory.C5
            },
            
            //6
            { 
                Theory.E2, Theory.F2, Theory.Fs2, Theory.G2, Theory.Gs2, Theory.A2, Theory.As2, Theory.B2, Theory.C3,
                Theory.Cs3, Theory.D3, Theory.Ds3, Theory.E3, Theory.F3, Theory.Fs3, Theory.G3
            }
        };

        static public double Pitch(int s, int f)
        {
            double result = 0;

            if (s <= 6 && f <= 15)
            {
                result = fretboard[s, f];
            }

            return result;
        }
    }

I've also added a new Note class which brings together the pitch and duration:

        public class Note
        {
            public double pitch;
            public double duration;

            public Note(double p, double d)
            {
                pitch = p;
                duration = d;
            }
        }

And the PlayMusic function has been improved now to use this to use the new Note and Fretboard classes.


        private void PlayMusic()
        {
            double bpm = 120;

            double barduration = 60 * 1000 * 4 / bpm; // in ms - assuming 4/4

            beat = barduration / 4;


            List<Note> tune = new List<Note>() 
            {

                new Note(Fretboard(0,1), 1.0),
                new Note(Fretboard(0,2), 1.0),
                new Note(Fretboard(0,3), 1.0),
                new Note(Fretboard(0,4), 1.0),
            };

            while (true)
            {
                foreach (Note n in tune)
                {
                    karp.pitch(n.pitch);
                    karp.pluck();
                    Thread.Sleep((int)(beat * n.duration));
                }
            }
        }

All good, but so far, not so interesting, so lets take some music - Silent Night - here in score and tab:


Which looks now like this in code:


            List<Note> tune = new List<Note>() 
            {
                new Note(Fretboard.Pitch(2,0), 0.75),
                new Note(Fretboard.Pitch(2,2), 0.25),
                new Note(Fretboard.Pitch(2,0), 0.5),
                new Note(Fretboard.Pitch(3,2), 1.5),

                new Note(Fretboard.Pitch(2,0), 0.75),
                new Note(Fretboard.Pitch(2,2), 0.25),
                new Note(Fretboard.Pitch(2,0), 0.5),
                new Note(Fretboard.Pitch(3,2), 1.5),

                new Note(Fretboard.Pitch(1,3), 1),
                new Note(Fretboard.Pitch(1,3), 0.5),
                new Note(Fretboard.Pitch(1,0), 1.5),

                new Note(Fretboard.Pitch(1,1), 1),
                new Note(Fretboard.Pitch(1,1), 0.5),
                new Note(Fretboard.Pitch(2,0), 1.5),

                new Note(Fretboard.Pitch(2,2), 0.5),
                new Note(Fretboard.Pitch(2,2), 0.5),
                new Note(Fretboard.Pitch(2,2), 0.5),
                new Note(Fretboard.Pitch(1,1), 0.75),
                new Note(Fretboard.Pitch(1,0), 0.25),
                new Note(Fretboard.Pitch(2,2), 0.5),

                new Note(Fretboard.Pitch(2,0), 0.75),
                new Note(Fretboard.Pitch(2,2), 0.25),
                new Note(Fretboard.Pitch(2,0), 0.5),
                new Note(Fretboard.Pitch(3,2), 1.5),

                new Note(Fretboard.Pitch(2,2), 1),
                new Note(Fretboard.Pitch(2,2), 0.5),
                new Note(Fretboard.Pitch(1,1), 0.75),
                new Note(Fretboard.Pitch(1,1), 0.25),
                new Note(Fretboard.Pitch(2,2), 0.5),

                new Note(Fretboard.Pitch(2,0), 0.75),
                new Note(Fretboard.Pitch(2,2), 0.25),
                new Note(Fretboard.Pitch(2,0), 0.5),
                new Note(Fretboard.Pitch(3,2), 1.5),

                new Note(Fretboard.Pitch(1,3), 0.5),
                new Note(Fretboard.Pitch(1,3), 0.5),
                new Note(Fretboard.Pitch(1,3), 0.5),
                new Note(Fretboard.Pitch(0,1), 0.75),
                new Note(Fretboard.Pitch(1,3), 0.25),
                new Note(Fretboard.Pitch(1,0), 0.5),

                new Note(Fretboard.Pitch(1,1), 1.5),
                new Note(Fretboard.Pitch(0,0), 1.5),

                new Note(Fretboard.Pitch(1,1), 0.75),
                new Note(Fretboard.Pitch(2,0), 0.25),
                new Note(Fretboard.Pitch(3,2), 0.5),
                new Note(Fretboard.Pitch(2,0), 0.75),
                new Note(Fretboard.Pitch(3,3), 0.25),
                new Note(Fretboard.Pitch(3,0), 0.5),

                new Note(Fretboard.Pitch(4,3), 1.5),
                new Note(0, 1.5),

                new Note(0, 3)
            };

A couple of little changes need to be made to the Karp class to accomodate DelayA and DelayL:

DelayL delay = new DelayL();


        public void pitch(double p)
        {
            if (p == 0)
            {
                delay.Length = 0;
            }
            else
            {
                Fo = p; // frequency
                double Fs = PcmStreamSource.SampleRate;

                double No = Fs / Fo;
                
                length = Helpers.Truncate(No - 0.5);
                excite = Helpers.Truncate(length * 0.95);

                delay.Length = No - 0.5;
            }
        }

And, remember to remove the delay.increment from after the delay.push as that has now been combined into that operation.

The other little tweak is that the decay value should be set to 0.9999 or even 0.99999 to make the notes ring out longer.

And it sounds like this..... a little buzzy, but fun!



No comments:

Post a Comment