Пример #1
0
 /** <summary>Get the singleton instance.</summary> */
 static public Metronome GetInstance()
 {
     if (Instance == null)
     {
         Instance = new Metronome();
     }
     return(Instance);
 }
Пример #2
0
        static void Main(string[] args)
        {
            // TODO: Main runs in a different thread than the audio streams. Deal with thread safety.

            // if a future hihat closed sound is not muted, calculate its position in byte time, var hhc.
            // (number of cycles * cycle size + value of old byteInterval at start of present cycle + new byteInterval)
            // in the hihat open sound thread, determine how many bytes until hhc is reached.
            Metronome metronome = Metronome.GetInstance();

            metronome.Tempo = 75f;
            //new Layer("[.25,.25@C4,.25@A2,.25@A3,.25@Bb2,1.75@Bb3]2,[.25@F2,.25@F3,.25@D2,.25@D3,.25@Eb2,1.75@Eb3](2)-.5,1/6@Eb3,1/6@D3,1/6@Db3,.5@C3,.5@Eb3,.5@D3,.5@Ab2,.5@G2,.5@Db3,1/6@C3,1/6@F#3,1/6@F3,1/6@E3,1/6@Bb3,1/6@A3,1/3@Ab3,1/3@Eb3,1/3@B2,1/3@Bb2,1/3@A2,1/3+3@Ab2,{$s}2/3", "C3", "", 0, .7f);
            //new Layer("[.5,.5+.75,.5,.75]4,.5,.25,.5,.25,.75,.25,.5,!1!.5,.5+.75,.5,.75!2!,.5,.75,.5,1,.25,{$s}2/3", WavFileStream.GetFileByName("Kick Drum V2"));
            //new Layer("[[1@0,1.5|.5]2,.25(2)]2,1@0,2,!1!1@0,.5,1.25!2.75!,.5,.75,1.5,.25(2),{$s}2/3", WavFileStream.GetFileByName("Snare Rim V3"));
            //new Layer("[.5(6)]7,{$s}2/3", WavFileStream.GetFileByName("HiHat Half Center V1"));
            //new Layer("1", "A6");

            new Layer(".5@0,[.5(3),.5@47,.25,.5,1.25]2,!3!.75,1,.75,.5@47,.25,1.5,!4!.5@47,1.5,.5,.25,{$s}2/3,{$s}4/5", WavFileStream.GetFileByName("Snare Center V2"));
            new Layer("[.75,3.25]3,.75,1.25,.75,1.25,{$s}2/3,{$s}4/5", WavFileStream.GetFileByName("Kick Drum V3"));
            new Layer("3*4+.25@0,.25@28,3.5,{$s}2/3,{$s}4/5", WavFileStream.GetFileByName("FloorTom V2"));
            new Layer(".25@0,[1,.5,.25@20,.25@22,.25@20,.5@22,.5,.25,.5]2,!3!.75,.5,1/6,1/6@12(2),.5,.25,.5,.75+1,!4!.5,.75,.25,.5(2),.5,{$s}2/3,{$s}4/5", WavFileStream.GetFileByName("HiHat Half Center V2"));
            new Layer("1", "A7");
            //new Layer("[.75@0,.75,.5,2]3,.75@0,.75,.5+.75,.75,.5", "A4");
            //new Layer("1", "D3");

            //metronome.SetSilentInterval(4, 4);
            //Metronome.Save("Slinky");
            //todo: why does @0 on first of pitch layer get played?
            //var layer1 = new Layer("[1,2/3,1/3]4,{$s}2/3", "A4");
            //new Layer("1", "A5");
            //var layer2 = new Layer("1,2/3,1/3", WavFileStream.GetFileByName("Ride Center V3"));
            //new Layer("1,1,1/3@19", WavFileStream.GetFileByName("HiHat Pedal V2"));
            //Metronome.Load("metronome");
            //var metronome = Metronome.GetInstance();
            //metronome.SetRandomMute(50);
            //Thread.Sleep(500);

            metronome.Play();
            //Console.ReadKey();
            //metronome.Tempo = 20f;
            //metronome.Record("test");
            //metronome.ExportAsWav(0, "test.wav");
            Console.ReadKey();
            metronome.Stop();

            //metronome.ChangeTempo(50f);
            //Console.ReadKey();
            //metronome.ChangeTempo(38f);
            //Console.ReadKey();
            //metronome.Stop();
            //metronome.Play();
            //Console.ReadKey();
            //metronome.Stop();
            metronome.Dispose();

            //Console.ReadKey();
        }
        /**<summary>Constructor</summary>
         * <param name="channel">Number of channels</param>
         * <param name="sampleRate">Samples per second</param>
         */
        public PitchStream(int sampleRate = 16000, int channel = 2)
        {
            waveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channel);
            // Default
            Frequency   = BaseFrequency = 440.0;
            Pan         = 0;
            BytesPerSec = waveFormat.AverageBytesPerSecond / 8;
            freqEnum    = Frequencies.Values.GetEnumerator();

            // set audible/silent interval if already exists
            if (Metronome.GetInstance().IsSilentInterval)
            {
                SetSilentInterval(Metronome.GetInstance().AudibleInterval, Metronome.GetInstance().SilentInterval);
            }
        }
Пример #4
0
 /** <summary>Add to soloed group.</summary> */
 public void ToggleSoloGroup()
 {
     if (IsSoloed)
     {
         // unsolo and close the solo group if this was the only member
         IsSoloed = false;
         if (Metronome.GetInstance().Layers.Where(x => x.IsSoloed == true).Count() == 0)
         {
             SoloGroupEngaged = false;
         }
     }
     else
     {
         // add this layer to solo group. all layers not in group will be muted.
         IsSoloed         = true;
         SoloGroupEngaged = true;
     }
 }
        protected bool IsRandomMuted()
        {
            bool result;

            if (!Metronome.GetInstance().IsRandomMute)
            {
                currentlyMuted = false;
                return(false);
            }

            // init countdown
            if (randomMuteCountdown == null && Metronome.GetInstance().RandomMuteSeconds > 0)
            {
                randomMuteCountdown = randomMuteCountdownTotal = Metronome.GetInstance().RandomMuteSeconds *BytesPerSec - initialOffset;
            }

            int rand = Metronome.GetRandomNum();

            if (randomMuteCountdown == null)
            {
                result = rand < Metronome.GetInstance().RandomMutePercent;
            }
            else
            {
                // countdown
                if (randomMuteCountdown > 0)
                {
                    randomMuteCountdown -= previousByteInterval;                          //previousByteInterval;
                }
                if (randomMuteCountdown < 0)
                {
                    randomMuteCountdown = 0;
                }

                float factor = (float)(randomMuteCountdownTotal - randomMuteCountdown) / randomMuteCountdownTotal;
                result = rand < Metronome.GetInstance().RandomMutePercent *factor;
            }

            return(result);
        }
 /**<summary>Reset state to default values.</summary>*/
 public void Reset()
 {
     freqEnum.Reset(); //= Frequencies.Values.GetEnumerator();
     BeatCollection.Enumerator = BeatCollection.GetEnumerator();
     ByteInterval   = 0;
     previousSample = 0;
     Gain           = Volume;
     if (Metronome.GetInstance().IsSilentInterval)
     {
         SetSilentInterval(Metronome.GetInstance().AudibleInterval, Metronome.GetInstance().SilentInterval);
     }
     if (Metronome.GetInstance().IsRandomMute)
     {
         randomMuteCountdown = null;
         currentlyMuted      = false;
     }
     if (Layer.Offset > 0)
     {
         SetOffset(
             BeatCell.ConvertFromBpm(Layer.Offset, this)
             );
     }
 }
Пример #7
0
        /** <summary>Layer constructor</summary>
         * <param name="baseSourceName">Name of the base sound source.</param>
         * <param name="beat">Beat code.</param>
         * <param name="offset">Amount of offset</param>
         * <param name="pan">Set the pan</param>
         * <param name="volume">Set the volume</param> */
        public Layer(string beat, string baseSourceName = null, string offset = "", float pan = 0f, float volume = 1f)
        {
            if (baseSourceName == null) // auto generate a pitch if no source is specified
            {
                SetBaseSource(GetAutoPitch());
            }
            else
            {
                SetBaseSource(baseSourceName);
            }

            if (offset != "")
            {
                SetOffset(offset);
            }
            Parse(beat); // parse the beat code into this layer
            Volume = volume;
            if (pan != 0f)
            {
                Pan = pan;
            }
            Metronome.GetInstance().AddLayer(this);
        }
Пример #8
0
        /**<summary>Get a random pitch based on existing pitch layers</summary>*/
        public string GetAutoPitch()
        {
            string note;
            byte   octave;

            string[] noteNames =
            {
                "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"
            };

            ushort[] intervals = { 3, 4, 5, 7, 8, 9 };

            do
            {
                // determine the octave
                octave = Metronome.GetRandomNum() > 49 ? (byte)5 : (byte)4;
                // 80% chance to make a sonorous interval with last pitch layer
                if (Metronome.GetRandomNum() < 80)
                {
                    var last  = Metronome.GetInstance().Layers.Last(x => IsPitch);
                    int index = Array.IndexOf(noteNames, last.BaseSourceName.TakeWhile(x => !char.IsNumber(x)));
                    index += intervals[Metronome.GetRandomNum() / (100 / 6)];
                    if (index > 11)
                    {
                        index -= 12;
                    }
                    note = noteNames[index];
                }
                else
                {
                    // randomly pick note
                    note = noteNames[Metronome.GetRandomNum() / (100 / 12)];
                }
            }while (Metronome.GetInstance().Layers.Where(x => x.IsPitch).Any(x => x.BaseSourceName == note + octave));

            return(note + octave);
        }
Пример #9
0
        /**<summary>Convert a quarter note time value into a byte count.</summary>
         * <param name="bpm">Number of quarter-notes.</param>
         * <param name="src">The audio source to get the bytes/second from.</param>
         */
        static public double ConvertFromBpm(double bpm, IStreamProvider src)
        {
            double result = bpm * (60d / Metronome.GetInstance().Tempo) * src.WaveFormat.SampleRate;

            return(result);
        }
Пример #10
0
        /** <summary>Parse the beat code, generating beat cells.</summary>
         * <param name="beat">Beat code.</param> */
        public void Parse(string beat)
        {
            ParsedString = beat;
            // remove comments
            beat = Regex.Replace(beat, @"!.*?!", "");

            if (beat.Contains('$'))
            {
                // prep single cell repeat on ref if exists
                beat = Regex.Replace(beat, @"($[\ds]+)(\(\d\))", "[$1]$2");
                //resolve beat referencing
                while (beat.Contains('$'))
                {
                    string refBeat;
                    // is a self reference?
                    if (beat[beat.IndexOf('$') + 1].ToString().ToLower() == "s" ||
                        Regex.Match(beat, @"\$(\d+)").Groups[1].Value == (Metronome.GetInstance().Layers.Count + 1).ToString())
                    {
                        refBeat = Regex.Replace(ParsedString, @"!.*?!", "");
                    }
                    else
                    {
                        //get the index of the referenced beat, if exists
                        int refIndex = int.Parse(Regex.Match(beat, @"\$[\d]+").Value.Substring(1)) - 1;
                        // does referenced beat exist?
                        refIndex = Metronome.GetInstance().Layers.ElementAtOrDefault(refIndex) == null ? 0 : refIndex;
                        refBeat  = Regex.Replace(Metronome.GetInstance().Layers[refIndex].ParsedString, @"!.*?!", "");

                        // remove sound source modifiers for non self references, unless its @0
                        refBeat = Regex.Replace(refBeat, @"@[a-gA-G]?[#b]?[1-9.]+", "");
                    }
                    // remove references and their innermost nest from the referenced beat
                    while (refBeat.Contains('$'))
                    {
                        if (Regex.IsMatch(refBeat, @"[[{][^[{\]}]*\$[^[{\]}]*[\]}][^\]},]*"))
                        {
                            refBeat = Regex.Replace(refBeat, @"[[{][^[{\]}]*\$[^[{\]}]*[\]}][^\]},]*", "");
                        }
                        else
                        {
                            refBeat = Regex.Replace(refBeat, @"\$[\ds]+,?", ""); // straight up replace
                        }
                    }
                    // clean out empty cells
                    refBeat = Regex.Replace(refBeat, @",,", ",");
                    refBeat = Regex.Replace(refBeat, @",$", "");

                    // replace in the refBeat
                    var match = Regex.Match(beat, @"\$[\ds]+");
                    beat = beat.Substring(0, match.Index) + refBeat + beat.Substring(match.Index + match.Length);
                }
            }

            // allow 'x' to be multiply operator
            beat = beat.Replace('x', '*');
            beat = beat.Replace('X', '*');

            // handle group multiply
            while (beat.Contains('{'))
            {
                var match = Regex.Match(beat, @"\{([^}]*)}([^,\]]+)"); // match the inside and the factor
                // insert the multiplication
                string inner = Regex.Replace(match.Groups[1].Value, @"(?<!\]\d*)(?=([\]\(\|,+-]|$))", "*" + match.Groups[2].Value);
                // switch the multiplier to be in front of pitch modifiers
                inner = Regex.Replace(inner, @"(@[a-gA-G]?[#b]?\d+)(\*[\d.*/]+)", "$2$1");
                // insert into beat
                beat = beat.Substring(0, match.Index) + inner + beat.Substring(match.Index + match.Length);
            }

            // handle single cell repeats
            while (Regex.IsMatch(beat, @"[^\]]\(\d+\)"))
            {
                var           match  = Regex.Match(beat, @"([.\d+\-/*]+@?[a-gA-G]?[#b]?\d*)\((\d+)\)([\d\-+/*.]*)");
                StringBuilder result = new StringBuilder(beat.Substring(0, match.Index));
                for (int i = 0; i < int.Parse(match.Groups[2].Value); i++)
                {
                    result.Append(match.Groups[1].Value);
                    // add comma or last term modifier
                    if (i == int.Parse(match.Groups[2].Value) - 1)
                    {
                        result.Append("+0").Append(match.Groups[3].Value);
                    }
                    else
                    {
                        result.Append(",");
                    }
                }
                // insert into beat
                beat = result.Append(beat.Substring(match.Index + match.Length)).ToString();
            }

            // handle multi-cell repeats
            while (beat.Contains('['))
            {
                var           match  = Regex.Match(beat, @"\[([^\][]+?)\]\(?(\d+)\)?([\d\-+/*.]*)");
                StringBuilder result = new StringBuilder();
                int           itr    = int.Parse(match.Groups[2].Value);
                for (int i = 0; i < itr; i++)
                {
                    // if theres a last time exit point, only copy up to that
                    if (i == itr - 1 && match.Value.Contains('|'))
                    {
                        result.Append(match.Groups[1].Value.Substring(0, match.Groups[1].Value.IndexOf('|')));
                    }
                    else
                    {
                        result.Append(match.Groups[1].Value);  // copy the group
                    }
                    if (i == itr - 1)
                    {
                        result.Append("+0").Append(match.Groups[3].Value);
                    }
                    else
                    {
                        result.Append(",");
                    }
                }
                result.Replace('|', ',');
                beat = beat.Substring(0, match.Index) + result.Append(beat.Substring(match.Index + match.Length)).ToString();
            }

            // fix instances of a pitch modifier being following by +0 from repeater
            beat = Regex.Replace(beat, @"(@[a-gA-G]?[#b]?[\d.]+)(\+[\d.\-+/*]+)", "$2$1");

            BeatCell[] cells = beat.Split(',').Select((x) =>
            {
                var match     = Regex.Match(x, @"([\d.+\-/*]+)@?(.*)");
                string source = match.Groups[2].Value;

                if (Regex.IsMatch(source, @"^[a-gA-G][#b]?\d{1,2}"))
                {
                    // is a pitch reference
                    return(new BeatCell(match.Groups[1].Value, source));
                }
                else // ref is a plain number. use as pitch or wav file depending on base source.
                {
                    if (IsPitch)
                    {
                        return(new BeatCell(match.Groups[1].Value, source));
                    }
                    else
                    {
                        return(new BeatCell(match.Groups[1].Value, source != "" ? WavFileStream.FileNameIndex[int.Parse(source), 0] : ""));
                    }
                }
            }).ToArray();

            SetBeat(cells);
        }