Jump to content

  • Log In with Google      Sign In   
  • Create Account


Procedural Music Generation


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
23 replies to this topic

#1 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 03 August 2011 - 03:54 PM

I've been working on an RPG that is procedurally generated (world, NPCs, story, questlines, items etc.) in my spare time and i have either working portions or conceptual models for each of them. One thing i have yet to completely figure out is what to do about music. First off, i know it can be done as there are great historical examples: the Emily Howell program being one of the best:

http://www.youtube.c...h?v=lOjV5eDXkyc

but exactly where to start from is a good quandry.

My best guess right now would be to feed a markov chain algorithm music samples of a style of music i want it to attempt to reproduce and tweak things from there, but even if i did get a bunch of notes, i need a decent way to go about either running them real-time (if i can do it fast enough) or creating a usable music file (if i cant do it fast enough) Ala .wave, .midi, etc.



So i guess to sum it up:

  • Has anyone had any experience in this area, what did you do?
  • What is a good way to write/play music procedurally (from c# if possible) [Libraries, APIs, etc]
  • Are there any open forums on this topic (aigamedev has this info paywalled. and im not sure i want to sub if i don't know if its going to help)


Sponsor:

#2 Álvaro   Crossbones+   -  Reputation: 12998

Like
1Likes
Like

Posted 03 August 2011 - 04:52 PM

So i guess to sum it up:

  • Has anyone had any experience in this area, what did you do?
  • What is a good way to write/play music procedurally (from c# if possible) [Libraries, APIs, etc]
  • Are there any open forums on this topic (aigamedev has this info paywalled. and im not sure i want to sub if i don't know if its going to help)


1. Not much experience, but I've read a couple of books on the subject. Of course, the opinions of anyone with actual experience doing this trump mine.

2. I've played around with a Python library called Mingus and I really liked the way it's structured. It has notions of note, interval, chord, bar... and it can also read and write MIDI files.

3. That interview at aigamedev ("Procedural Music Generation for Games using Statistical Models") is actually not bad. I can tell you the basic ideas in it. The discussion concentrated on how to generate a single melodic line. There were a couple of comments about coordinating instruments, but it wasn't detailed enough to reproduce. They take a MIDI file as input (or several MIDI files, but their simple approach only works well if they are extremely similar styles) and consider a sequence of notes as being a sequence of symbols (they call them "states", but I think "symbols" is more clear, because they play the role of letters in a name generator). A symbol contains the following information: pitch, duration, and part of the bar in which the note starts (including this is a key idea). They accumulate statistics about which symbols follow which other symbols, and then proceed to generate music following the same distributions (a.k.a., they use a Markov model). They can also consider that the state consists of the last several consecutive symbols (e.g., 2 or 3 of them), like you probably would do in a name generator (the number of symbols that constitute the state is often referred to as the "order").

I feel that the results are too similar to the input and lack overall structure. If I were to do something like this, I would probably take multiple MIDI files, transpose them so they share a key, enrich the input with chord notations and then have two Markov chains: A high-level one that generates chord changes and a low-level one that generates notes in the manner described above, but I would make the chord part of the symbol, and perhaps the next chord as well. I can see something like that being able to generate very believable jazz, for instance. You can also generate the chord changes in a more prescribed fashion (12-bar blues, boogie, "rhythm changes", etc.).

#3 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 03 August 2011 - 05:16 PM

Thanks for the info, I'll have to check Mingus out. Also good to know i was somewhat on target with the Markov based approach, but how you described it makes a lot of sense in setting up the contributing markov models. I'll give it a shot. If / When i get something working, I'll post a journal about it.

#4 dutt   Members   -  Reputation: 103

Like
0Likes
Like

Posted 04 August 2011 - 02:24 AM

Thanks for the info, I'll have to check Mingus out. Also good to know i was somewhat on target with the Markov based approach, but how you described it makes a lot of sense in setting up the contributing markov models. I'll give it a shot. If / When i get something working, I'll post a journal about it.


Please give a shout if you get something running, it'd be interesting to see how you did it, I'll add a watch to this thread :)
trassel (thread lib)

#5 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 05 August 2011 - 02:00 PM

Well, I've got phase1 of the Markov Token Engine working: a basic markov chain text parser (hey, ya gatta walk before you run).

Anyway, I fed it "A Midsummer Night's Dream" as the input text doing a 1-4 Token Association and here is an excerpt:

"he stay, whom love doth the moon shine that you should think, we shadows have offended, Think no more of this discord? PHILOSTRATE A play the lion too: I have come, great clerks have purposed To greet me with premeditated welcomes; Where I have seen through the lion's neck: and he himself must have a wall in wax By him imprinted and within his power I am made bold, Nor how it may extenuate-- To death, or to a vow of maiden's patience."

Not bad.

So, this guy is pretty useful already, and He'll end up in my game as a name generator for sure for my NPC generator. For those of you whom are curious, I offer you the code to play with:


"NOTE: if your text isnt large enough, you increase the chance of circular loops in your distribution, if you see lots of repeating words, that is why"

"NOTE2: the last 'n' tokens in the input text are non-associated with any tokens, but the Length-n-1 token is, and the last 'n' tokens are added to its association... if the final token in the chain is a unique word, you may get issues if the Length-n-1 token is ever chosen"

"NOTE3: the code is a bit dirty, so I'm sure it can be improved upon, and there may even be a bug or two.


 public class MarkovChain
    {
        /// <summary>
        /// dictionary of token associations
        /// </summary>
        private Dictionary<String, List<String>> mc_MarkovTokens = new Dictionary<String, List<String>>();

        /// <summary>
        /// basic random number generator
        /// </summary>
        private Random mc_Rand = new Random();

        //up to how many follow-on tokens we want to associate per token
        //NOTE - doesnt always guarantee 4, i've seen 3 and 2 (likely a whitespace cleanup error somewhere)
        private int n = 4;

        /// <summary>
        /// basic constructor
        /// </summary>
        public MarkovChain(){}

        /// <summary>
        /// parses a file into a markov token dictionary
        /// </summary>
        /// <param name="fileName"></param>
        public void parse(String fileName)
        {

            //get access to the input file
            FileStream fs = new FileStream(fileName, FileMode.Open);
            
            //setup the stream reader
            StreamReader sr = new StreamReader(fs);

            //holds tokens in work
            String[] tokenArray;

            //the text following the token
            String followingText = "";

            //create a word token placeholder for carryovers
            String tokenCarry = "";

            //keep reading the file until you reach the end
            while (!sr.EndOfStream)
            {
                //check to see if a token was carried over from last loop
                if (tokenCarry == "")
                {
                    //replace token carry with the line
                    tokenCarry = sr.ReadLine();
                }
                else
                {
                    //combine with tokenCarry 
                    tokenCarry += " " + sr.ReadLine();
                }
                
                //split tokenCarry into tokens
                tokenArray = tokenCarry.Trim().Split(' ');
                
                //create tokens and store them into the markov Token Dictionary
                for (int i = 0; i < tokenArray.Length; i++)
                {
                    //ensure you have room to define a new token entry
                    //[t1][t2 t3 - tn]<[EOL]
                    if ((i + n) < (tokenArray.Length - 1))
                    {
                        //calculate the following text
                        for (int j = 1; j <= n; j++)
                        {
                            followingText += tokenArray[i + j].Trim() + " ";
                        }

                        //check to see if the token already exists
                        if (mc_MarkovTokens.ContainsKey(tokenArray[i].ToLower()))
                        {
                            //it does exist, so add the new token association
                            mc_MarkovTokens[tokenArray[i].Trim().ToLower()].Add(followingText.Trim());
                        }
                        else
                        {
                            //it doesnt exist, so create it and its token association
                            mc_MarkovTokens.Add(tokenArray[i].Trim().ToLower(), new List<String>());
                            mc_MarkovTokens[tokenArray[i].Trim().ToLower()].Add(followingText.Trim());
                        }
                        //reset following text
                        followingText = "";
                    }
                    else
                    {
                        //calculate the following text
                        for (int j = i; j < tokenArray.Length; j++)
                        {
                            followingText += tokenArray[j].Trim() + " ";
                        }

                        //carry over the training text to the next iteration
                        tokenCarry = followingText.Trim();
                        followingText = "";
                        break;
                    }
                }
            }

            //it is likely we missed tokens near the end, do the same thing as we did above, but for them
            if (tokenCarry.Length > 0)
            {
                tokenArray = tokenCarry.Trim().Split(' ');

                //calculate the following text
                for (int j = 1; j < tokenArray.Length; j++)
                {
                    followingText += tokenArray[j].Trim() + " ";
                }

                if (mc_MarkovTokens.ContainsKey(tokenArray[0].ToLower()))
                {
                    mc_MarkovTokens[tokenArray[0].Trim().ToLower()].Add(followingText.Trim());
                }
                else
                {
                    mc_MarkovTokens.Add(tokenArray[0].Trim().ToLower(), new List<String>());
                    mc_MarkovTokens[tokenArray[0].Trim().ToLower()].Add(followingText.Trim());
                }
            }

            //close the streams
            sr.Close();
            fs.Close();
        }

        /// <summary>
        /// outputs a file with generated text based on the current token association dictionary
        /// </summary>
        /// <param name="fileName"></param>
        public void output(String fileName)
        {
            //start with "he" as 1st token (its common in use)
            String token = "he";
            List<String> tokens;
            String output = token;
            int index = 0;

            //get 1000 token pairs
            for (int i = 0; i < 1000; i++)
            {
                //find the first token
                tokens = mc_MarkovTokens[token.ToLower()];
                
                //select a random index within its associated list 
                //(this also keeps us within distribution parameters since i allow duplicate entries)
                index = mc_Rand.Next(0, tokens.Count - 1);
                
                //get the last word from the associated list, make it the next token
                token = tokens[index].Split(' ')[tokens[index].Split(' ').Length - 1];
                
                //append the chosen token list to the output string
                output += " " + tokens[index];
            }

            //create a file access stream
            FileStream fs = new FileStream(fileName, FileMode.Create);
            //create a stream writier to write the string
            StreamWriter sw = new StreamWriter(fs);

            //write the file
            sw.Write(output);

            //close the streams
            sw.Close();
            fs.Close();
        }


    }


I guess phase 2 will involve learning as much about various music formats and how to turn them into tokenizable symbols to feed to a similar (but not the same) Tokenizer like i have above. From there i can goto phase 3 - initial crappy music :D

#6 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 07 August 2011 - 04:38 AM

Ok, for part 1 of Phase 2, I've decided to go with ABC Music notation as the input standard. Why? The format is already symbol based, has defined musical notes, measures, timings, etc in a human readable and token parsable format. Could I use midi, perhaps, but i would still need to translate the bitstreams into symbols to be tokenized, thus making ABC the better choice for experimentation, as well as the fact i have TONS of these already for use in LoTRO's built-in music system.
For more on the ABC format here: http://en.wikipedia....ki/ABC_notation and here http://abcnotation.com/blog/2009/12/23/how-to-get-started-with-abc-notation/ and here http://abcnotation.com/blog/2010/01/31/how-to-understand-abc-the-basics/

Next Steps: Take the ABC format and build a parser from textual symbols to logical symbols as notes have more meaning than just abcdefg

#7 Net Gnome   Members   -  Reputation: 769

Like
2Likes
Like

Posted 07 August 2011 - 10:32 AM

Hah! Moderate Victory! The choice of ABC format was a success! With almost no tweaks, I've gotten my token parser to read ABC files and generate output. I then confirmed quality by loading up LoTRO (Lord of The Rings Online) and having my character play them. And it Worked! The files resemble the source material and flow together (for the most part). While not concert quality, it produced ABC readible & playable music that did not sound entirely random. So i guess i skipped Phase 2 pt 2 straight to Phase 3: crappy music. Now all is needed is to get the generator to learn multiple similar music scores and attempt to build a song out of them, additionally, I'll need to create 3-4 different parsings and dictionaries based on different token lengths, and then assemble them together based on a rule set to create a more harmonious music stream output.

Anyway, Source material i used was the following:


X:1
T:Chrono Cross - Timescar
L:1/4
Q:110
K:C
[E,4z/2] B,/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2
D/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 D/2 [^F,3/2z/2] G,/2 D/2
[E,4z/2] B,/2 ^F,/2 G,/2 E2 [eE,4z/2] B,/2 [b^F,/2] G,/2 [bD/2] ^F,/2
[a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2
[^F,/2z/4] g/8 a/8 [g/2G,/2] [^f/2D/2] [^f3/4C,4z/2] [G,/2z/4] g/4
[e7^F,/2] G,/2 E/2 ^F,/2 G,/2 E/2 [C,4z/2] G,/2 ^F,/2 G,/2 E/2 ^F,/2
G,/2 E/2 [eE,4z/2] B,/2 [b^F,/2] G,/2 [bD/2] ^F,/2 [b/2G,/2]
[^c/2D/2] [d/2E,4] [e/4B,/2] d/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2]
^F,/2 [a/2G,/2] [g/2D/2] [a/2=F,4] [b/4C,/2] a/8 b/8 [a5A,/2] C,/2
G,/2 C,/2 A,/2 C,/2 [F,4z/2] C,/2 A,/2 C,/2 [aG,/2] C,/2 [bA,/2] C,/2
[g/2E,4] [^f/2B,/2] [g5^F,/2] G,/2 D/2 ^F,/2 G,/2 B,/2 [E,3z/2] B,/2
^F,/2 G,/2 [g^F/8] E/4 D/8 A,/2 [a^F,E,/2] A,/2 [b3/2G,2z/2] D,/2
A,/2 [g/2B,/2] [a2D,2z/2] A,/2 E,/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2
[^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 [d/2G,/2] [b11/8G,2z/2] D,/2
A,/2 [b/8B,/2] c'/4 b/8 [a5/8D,2z/2] [A,/2z/4] [g3/4z/4] E,/2
[^f/2^F,/2] [e3C,/2] [C,7/2z/2] ^F,/2 G,/2 [E2z] ^f/8 e3/8 d/2
[e4E,3/4] [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4
[E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 [E/4A/4E,/4B,/4]
[B/4E,/4B,/4] [B/4E,/4B,/4] [B/4E,/4B,/4] [B/4E,/4B,/4] [B/4E,/4B,/4]
[B/4E,/4B,/4D,/2] [B/4E,/4B,/4] [E/4B/4E,/4B,/4] [=c/4E,/4C/4]
[c/4E,/4C/4] [c/4E,/4C/4] [c/4E,/4C/4] [c/4E,/4C/4B,/4]
[c/4E,/4C/4D,/2] [c/4E,/4C/4] [G/4c/4E,3/4G,/4C/4] [d/4G,/4D/4]
[d/4G,/4D/4] [d/4G,/4D/4E,5/4] [d/4G,/4D/4] [d/4G,/4D/4B,/4]
[d/4G,/4D/4D,/2] [d/4G,/4D/4] [G/4d/4G,/4D/4E,3/4] [d/4G,/4D/4]
[d/4G,/4D/4] [e/4G,/4E/4E,5/4] [d/4G,/4D/4] [c/4G,/4C/4B,/4]
[B/4G,/4B,/4D,/2] [A/4G,/4A,/4] [E/4A/4E,/4B,/4] [B/4E,/4B,/4]
[B/4E,/4B,/4] [B/4E,/4B,/4] [B/4E,/4B,/4] [B/4E,/4B,/4]
[B/4E,/4B,/4D,/2] [B/4E,/4B,/4] [E/4B/4E,/4B,/4] [c/4E,/4C/4]
[c/4E,/4C/4] [c/4E,/4C/4] [c/4E,/4C/4] [c/4E,/4C/4B,/4]
[c/4E,/4C/4D,/2] [c/4E,/4C/4] [G/4c/4E,3/4G,/4C/4] [d/4G,/4D/4]
[d/4G,/4D/4] [d/4G,/4D/4E,5/4] [d/4G,/4D/4] [d/4G,/4D/4B,/4]
[d/4G,/4D/4D,/2] [d/4G,/4D/4] [G/4d/4G,/4D/4E,3/4] [d/4G,/4D/4]
[d/4G,/4D/4] [e/4G,/4E/4E,5/4] [d/4G,/4D/4] [c/4G,/4C/4B,/4]
[e/2G,/4B,/4D,/2] [G,/4A,/4] [b13/8E,3/4E/4A/4] [E/4B/4] [E/4B/4]
[E/4B/4E,5/4] [E/4B/4] [E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4]
[a5/2E/4B/4E,3/4] [E/4c/4] [E/4c/4] [E/4c/4E,5/4] [E/4c/4]
[E/4c/4B,/4] [E/4c/4D,/2] [E/4c/4] [E,3/4G/4c/4] [G/4d/4] [g/8G/4d/4]
[a/4z/8] [G/4d/4E,5/4z/8] g/8 [^f/2G/4d/4] [G/4d/4B,/4]
[g/2G/4d/4D,/2] [G/4d/4] [^f/2G/4d/4E,3/4] [G/4d/4] [d/2G/4]
[G/4e/4E,5/4] [e/2G/4d/4] [G/4c/4B,/4] [B/2G/4D,/2] [G/4A/4]
[b13/8E,3/4E/4A/4] [E/4B/4] [E/4B/4] [E/4B/4E,5/4] [E/4B/4]
[E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4] [a13/8E/4B/4E,3/4] [E/4c/4]
[E/4c/4] [E/4c/4E,5/4] [E/4c/4] [E/4c/4B,/4] [g/8E/4c/4D,/2] [a/4z/8]
[E/4c/4z/8] g/8 [^f/2E,3/4G/4c/4] [G/4d/4] [g/2G/4d/4] [G/4d/4E,5/4]
[d/4G/4] [G/4d/4B,/4] [G/4d/4D,/2] [G/4d/4] [G/4d/4E,3/4] [G/4d/4]
[G/4d/2] [G/4e/4E,5/4] [G/4d/2] [G/4c/4B,/4] [e/2G/4B/4D,/2] [G/4A/4]
[b13/8E,3/4E/4A/4] [E/4B/4] [E/4B/4] [E/4B/4E,5/4] [E/4B/4]
[E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4] [a5/2E/4B/4E,3/4] [E/4c/4]
[E/4c/4] [E/4c/4E,5/4] [E/4c/4] [E/4c/4B,/4] [E/4c/4D,/2] [E/4c/4]
[E,3/4G/4c/4] [G/4d/4] [g/8G/4d/4] [a/4z/8] [G/4d/4E,5/4z/8] g/8
[^f/2G/4d/4] [G/4d/4B,/4] [g/2G/4d/4D,/2] [G/4d/4] [^f/2G/4d/4E,3/4]
[G/4d/4] [d/2G/4] [G/4e/4E,5/4] [e/2G/4d/4] [G/4c/4B,/4] [B/2G/4D,/2]
[G/4A/4] [b13/8E,3/4E/4A/4] [E/4B/4] [E/4B/4] [E/4B/4E,5/4] [E/4B/4]
[E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4] [a13/8E/4B/4E,3/4] [E/4c/4]
[E/4c/4] [E/4c/4E,5/4] [E/4c/4] [E/4c/4B,/4] [g/8E/4c/4D,/2] [a/4z/8]
[E/4c/4z/8] g/8 [^f/2E,3/4G/4c/4] [G/4d/4] [g/2G/4d/4] [G/4d/4E,5/4]
[d/4G/4] [G/4d/4B,/4] [G/4d/4D,/2] [G/4d/4] [G/4d/4E,3/4] [G/4d/4]
[G/4d/2] [G/4e/4E,5/4] [G/4d/2] [G/4c/4B,/4] [d/2G/4B/4D,/2] [G/4A/4]
[e7/2C,3/4G/4c/4] [G/4c/4] [G/4c/4] [G/4c/4C,3/4] [G/4c/4]
[G/4c/4G,/4] [G/4c/4C,/2] [G/4c/4] [D,3/4G/4d/4] [G/4d/4] [G/4d/4]
[G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [d/4G/4D,/2] [c'/4G/4d/4]
[b17/8E,3/4G/4e/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4]
[G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] [a17/8G/4d/4E,3/4] [G/4d/4]
[G/4d/4] [G/4d/4E,5/4] [G/4d/4] [G/4d/4B,/4] [G/4d/4D,/2] [G/4d/4]
[g7/2C,3/4G/4c/4] [G/4c/4] [G/4c/4] [G/4c/4C,3/4] [G/4c/4]
[G/4c/4G,/4] [G/4c/4C,/2] [G/4c/4] [D,3/4G/4d/4] [G/4d/4] [G/4d/4]
[G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [^f/4G/4d/4D,/2] [g/4G/4d/4]
[d2E,3/4G/4e/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4B,/4]
[G/4e/4D,/2] [G/4e/4] [b17/8G/4d/4E,3/4] [G/4d/4] [G/4d/4]
[G/4d/4E,5/4] [G/4d/4] [G/4d/4B,/4] [G/4d/4D,/2] [G/4d/4]
[e7/2C,3/4G/4c/4] [G/4c/4] [G/4c/4] [G/4c/4C,3/4] [G/4c/4]
[G/4c/4G,/4] [G/4c/4C,/2] [G/4c/4] [D,3/4G/4d/4] [G/4d/4] [G/4d/4]
[G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [d/4G/4D,/2] [c'/4G/4d/4]
[b17/8E,3/4G/4e/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4]
[G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] [a17/8G/4d/4E,3/4] [G/4d/4]
[G/4d/4] [G/4d/4E,5/4] [G/4d/4] [G/4d/4B,/4] [G/4d/4D,/2] [G/4d/4]
[g13/8C,3/4G/4c/4] [G/4c/4] [G/4c/4] [G/4c/4C,3/4] [G/4c/4]
[G/4c/4G,/4] [a/4G/4c/4C,/2] [b/4G/4c/4] [a13/8D,3/4G/4d/4] [G/4d/4]
[G/4d/4] [G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [^f/2G/4d/4D,/2]
[G/4d/4] [g/4E,3/4G/4e/4] [^f/4G/4e/4] [e/4G/4] [G/4e/4E,5/4]
[G/4e/4] [G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] [G/4e/4E,3/4] [G/4e/4]
[G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4]
[e3/4C,2z/4] b/4 g/4 e/4 [e3/4z/4] b/4 g/4 e/4 [D,2e3/4z/4] b/4 g/4
e/4 [e/2z/4] b/4 [d/4g/4] [c'/4e/4] [b/4E,2e/4] [bz/4] g/4 e/4 e/4
[b7/8z/4] g/4 e/4 [a17/8D,2e/4] b/4 g/4 e/4 e/4 b/4 g/4 e/4
[g/2C,2e/4] b/4 [gz/4] e/4 e/4 b/4 [gz/4] e/4 [D,2e/4] b/4 [gz/4] e/4
e/4 b/4 [^f/4g/4] [g/4e/4] [d17/8E,3e/4] b/4 g/4 e/4 e/4 b/4 g/4 e/4
[b/4e/4] [bz/4] g/4 e/4 [D,e/4] [b7/8z/4] g/4 e/4 [e3/4C,2z/4] b/4
g/4 e/4 [e3/4z/4] b/4 g/4 e/4 [D,2e3/4z/4] b/4 g/4 e/4 [e/2z/4] b/4
[d/4g/4] [c'/4e/4] [b/4E,3e/4] [bz/4] g/4 e/4 e/4 [b7/8z/4] g/4 e/4
[a17/8e/4] b/4 g/4 e/4 [D,e/4] b/4 g/4 e/4 [g/2C,2e/4] b/4 [gz/4] e/4
e/4 b/4 [a/4g/4] [b/4e/4] [a13/8D,2e/4] b/4 g/4 e/4 e/4 b/4 [^f/2g/4]
e/4 [g/4E,/2E/2e/2] [^f/4b/4] [e/4E/2g/4E,/2] e/4 [E/2e/2E,/2z/4] b/4
[E/2e/4g/4E,/2] e/4 [E/4e/4E,/4] [E/4e/4b/4E,/4] [E/4e/4g/4E,/4]
[E/4e/4E,/4] [E/4e/4E,/4] [E/4e/4b/4E,3/4] [E/4e/4g/4] [E/4e/4]
[e3/4C,3/4G/4c/4] [G/4c/4b/4] [G/4c/4g/4] [G/4c/4e/4C,3/4]
[G/4c/4e3/4] [G/4c/4b/4G,/4] [G/4c/4g/4C,/2] [G/4c/4e/4]
[D,3/4G/4d/4e3/4] [G/4d/4b/4] [G/4d/4g/4] [G/4d/4e/4D,3/4]
[G/4d/4e/2] [G/4d/4b/4A,/4] [d/4G/4g/4D,/2] [c'/4G/4d/4e/4]
[b/4E,3/4G/4e/4] [G/4e/4b] [G/4e/4g/4] [G/4e/4E,5/4] [G/4e/4]
[G/4e/4b7/8B,/4] [G/4e/4g/4D,/2] [G/4e/4] [a17/8G/4d/4e/4E,3/4]
[G/4d/4b/4] [G/4d/4g/4] [G/4d/4e/4E,/2] [D,/2G/4d/4e/4]
[G/4d/4b/4B,/4] [G/4d/4g/4D,/2] [G/4d/4e/4] [g/2C,3/4G/4e/4]
[G/4e/4b/4] [G/4e/4g] [G/4e/4C,3/4] [G/4e/4] [G/4e/4b/4G,/4]
[G/4e/4gC,/2] [G/4e/4] [D,3/4G/4d/4e/4] [G/4d/4b/4] [G/4d/4g]
[G/4d/4e/4D,3/4] [G/4d/4e/4] [G/4d/4b/4A,/4] [^f/4G/4d/4g/4D,/2]
[g/4G/4d/4e/4] [d2E,3/4G/4e/4] [G/4e/4b/4] [G/4e/4g/4] [G/4e/4E,5/4]
[G/4e/4] [G/4e/4b/4B,/4] [G/4e/4g/4D,/2] [G/4e/4] [b/4G/4d/4e/4E,3/4]
[G/4d/4b] [G/4d/4g/4] [G/4d/4e/4E,/2] [D,/2G/4d/4e/4]
[G/4d/4b7/8B,/4] [G/4d/4g/4D,/2] [G/4d/4e/4] [e3/4C,3/4G/4c/4]
[G/4c/4b/4] [G/4c/4g/4] [G/4c/4e/4C,3/4] [G/4c/4e3/4] [G/4c/4b/4G,/4]
[G/4c/4g/4C,/2] [G/4c/4e/4] [D,3/4G/4d/4e3/4] [G/4d/4b/4] [G/4d/4g/4]
[G/4d/4e/4D,3/4] [G/4d/4e/2] [G/4d/4b/4A,/4] [d/4G/4g/4D,/2]
[c'/4G/4d/4e/4] [b/4E,3/4G/4e/4] [G/4e/4b] [G/4e/4g/4] [G/4e/4E,5/4]
[G/4e/4] [G/4e/4b7/8B,/4] [G/4e/4g/4D,/2] [G/4e/4]
[a17/8G/4d/4e/4E,3/4] [G/4d/4b/4] [G/4d/4g/4] [G/4d/4e/4E,/2]
[D,/2G/4d/4e/4] [G/4d/4b/4B,/4] [G/4d/4g/4D,/2] [G/4d/4e/4]
[g/2C,3/4G/4c/4e/4] [G/4c/4e/4b/4] [G/4c/4e/4g] [G/4c/4e/4C,3/4]
[G/4c/4e/4] [G/4c/4e/4b/4G,/4] [a/4G/4c/4e/4g/4C,/2] [b/4G/4c/4e/4]
[a/2D,3/4G/4c/4^f/4] [G/4c/4^f/4d/4] [G/4c/4^f/4a] [G/4c/4^f/4D,3/4]
[G/4c/4^f/4] [G/4c/4^f/4d/4A,/4] [d/2G/4c/4^f/4a/4D,/2] [G/4c/4^f/4]
[e/2E,/2E/2^G/2B/2] 


I Then used this, cut off the header portion and fed it to the Markov code i created and the re-attached the header after trimming some spurrious repeats at the end. I got this:


X:1
T:Chrono Cross - Timescar (PROCEDURAL GENERATED VERSION)
L:1/4
Q:110
K:C
[e,4z/2] B,/2 ^F,/2 G,/2 ^F,/2 G,/2 E/2 ^F,/2 G,/2 E/2 [C,4z/2] G,/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 [bD/2] ^F,/2 [a/2G,/2] 

[g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E/2 [C,4z/2] G,/2 ^F,/2 G,/2 C,/2 A,/2 C,/2 

A,/2 C,/2 [aG,/2] C,/2 [bA,/2] C,/2 [F,4z/2] C,/2 A,/2 C,/2 [aG,/2] C,/2 [F,4z/2] C,/2 A,/2 E,/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 

[e11/8E/2] D/2 C/2 [d/2G,/2] [b11/8G,2z/2] D,/2 A,/2 [b3/2G,2z/2] D,/2 A,/2 E,/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 

[d/2G,/2] [b11/8G,2z/2] D,/2 A,/2 [g/2B,/2] [a2D,2z/2] A,/2 E,/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 [d/2G,/2] 

[b11/8G,2z/2] D,/2 A,/2 E,/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 [d/2G,/2] [b11/8G,2z/2] D,/2 A,/2 E,/2 ^F,/2 

[g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 [d/2G,/2] [b11/8G,2z/2] D,/2 A,/2 C,/2 [F,4z/2] C,/2 A,/2 C,/2 [F,4z/2] C,/2 A,/2 C,/2 A,/2 

C,/2 [F,4z/2] C,/2 A,/2 C,/2 [F,4z/2] C,/2 A,/2 C,/2 [F,4z/2] C,/2 [aG,/2] C,/2 [bA,/2] C,/2 [g/2E,4] [^f/2B,/2] [g5^F,/2] G,/2 D/2 [E,4z/2] B,/2 ^F,/2 

[b/2G,/2] [^c/2D/2] [d/2E,4] [e/4B,/2] d/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 [b/2G,/2] [^c/2D/2] [d/2E,4] [e/4B,/2] d/8 e/8 [d/2^F,/2] [^c/2G,/2] 

[bD/2] ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E/2 [eE,4z/2] B,/2 [b^F,/2] G,/2 [g^F/8] E/4 D/8 e/8 

[d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E/2 ^F,/2 G,/2 E/2 [C,4z/2] G,/2 

^F,/2 G,/2 D/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 C,/2 A,/2 C,/2 A,/2 C,/2 [aG,/2] C,/2 [bA,/2] C,/2 G,/2 C,/2 A,/2 [b3/2G,2z/2] D,/2 A,/2 [a^F,E,/2] A,/2 

[b3/2G,2z/2] D,/2 A,/2 [g/2B,/2] [a7/4^F,/2] G,/2 D/2 ^F,/2 G,/2 D/2 [^F,/2z/4] g/8 a/8 [g/2G,/2] [^f/2D/2] [^f3/4C,4z/2] [G,/2z/4] g/4 [e7^F,/2] G,/2 

E/2 ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [^F,3/2z/2] G,/2 D/2 [E,4z/2] B,/2 [b^F,/2] G,/2 [bD/2] ^F,/2 [a/2G,/2] [g/2D/2] 

[^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [^F,/2z/4] g/8 a/8 [g/2G,/2] [^f/2D/2] [^f3/4C,4z/2] [G,/2z/4] g/4 [e7^F,/2] G,/2 E/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 

C/2 [^f/8D/2] g/4 [e7^F,/2] G,/2 E/2 [C,4z/2] G,/2 ^F,/2 G,/2 E/2 ^F,/2 G,/2 B,/2 [E,3z/2] B,/2 ^F,/2 G,/2 E/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 

E2 [eE,4z/2] B,/2 [b^F,/2] G,/2 [bD/2] ^F,/2 [b/2G,/2] [^c/2D/2] [d/2E,4] [e/4B,/2] d/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 [b/2G,/2] [^c/2D/2] 

[d/2E,4] [e/4B,/2] d/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 D/2 

^F,/2 G,/2 D/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 E/2 [C,4z/2] G,/2 ^F,/2 G,/2 [g^F/8] E/4 D/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 [a/2G,/2] [g/2D/2] 

[^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E/2 [eE,4z/2] B,/2 [b^F,/2] G,/2 D/2 [^F,/2z/4] g/8 [^f/2G/4d/4] [G/4d/4B,/4] 

[g/2G/4d/4D,/2] [G/4d/4] [^f/2G/4d/4E,3/4] [G/4d/4] [e7/2C,3/4G/4c/4] [G/4c/4] [G/4c/4] [G/4c/4C,3/4] [G/4c/4] [G/4c/4G,/4] [G/4c/4C,/2] [G/4c/4] 

[D,3/4G/4d/4] [G/4d/4] [G/4d/4] [G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [^f/4G/4d/4D,/2] [g/4G/4d/4] [d2E,3/4G/4e/4] [G/4e/4] [G/4e/4b/4B,/4] 

[G/4e/4g/4D,/2] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4B,/4] [G/4e/4D,/2] 

[G/4e/4] [D,3/4G/4d/4e/4] [G/4d/4b/4] [G/4d/4g] [G/4d/4e/4D,3/4] [G/4d/4e/4] [G/4d/4b/4A,/4] [d/4G/4g/4D,/2] [c'/4G/4d/4e/4] [b/4E,3/4G/4e/4] [G/4e/4b] 

[G/4e/4g/4] [G/4e/4E,5/4] [e/2G/4d/4] [G/4c/4B,/4] [B/2G/4D,/2] [G/4A/4] [b13/8E,3/4E/4A/4] [E/4B/4] [a5/2E/4B/4E,3/4] [E/4c/4] [E/4c/4] [E/4c/4E,5/4] 

[E/4c/4] [E/4c/4B,/4] [E/4c/4D,/2] [E/4c/4] [E,3/4G/4c/4] [G/4d/4] [g/8G/4d/4] [a/4z/8] [G/4d/4E,5/4z/8] g/8 [^f/2G/4d/4] [G/4d/4B,/4] [g/2G/4d/4D,/2] 

[G/4d/4] [d/2G/4] [G/4e/4E,5/4] [e/2G/4d/4] [G/4c/4B,/4] [B/2G/4D,/2] [G/4A/4] [b13/8E,3/4E/4A/4] [E/4B/4] [E/4B/4] [E/4B/4B,/4] [e/2E/4B/4D,/2] 

[E/4B/4] [E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4] [E/4B/4E,5/4] [E/4B/4] [E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4] [a5/2E/4B/4E,3/4] [E/4c/4] [E/4c/4] 

[E/4c/4E,5/4] [E/4c/4] [E/4c/4B,/4] [g/8E/4c/4D,/2] [a/4z/8] [E/4c/4z/8] g/8 [^f/2G/4d/4] [G/4d/4B,/4] [g/2G/4d/4D,/2] [G/4d/4] [^f/2G/4d/4E,3/4] 

[G/4d/4] [G/4d/4A,/4] [^f/2G/4d/4D,/2] [G/4d/4] [G/4d/4A,/4] [d/4G/4D,/2] [c'/4G/4d/4] [b17/8E,3/4G/4e/4] [G/4e/4] [G/4e/4] [G/4e/4b7/8B,/4] 

[G/4e/4g/4D,/2] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [b17/8G/4d/4E,3/4] [G/4d/4] [G/4d/4] [G/4d/4E,3/4] [G/4d/4] [G/4d/2] [G/4e/4E,5/4] [G/4d/2] 

[G/4c/4B,/4] [B/2G/4D,/2] [G/4A/4] [b13/8E,3/4E/4A/4] [E/4B/4] [E/4B/4] [E/4B/4E,5/4] [E/4B/4] [E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4] [a5/2E/4B/4E,3/4] 

[E/4c/4] [E/4c/4E,5/4] [E/4c/4] [E/4c/4B,/4] [g/8E/4c/4D,/2] [a/4z/8] [E/4c/4z/8] g/8 [^f/2E,3/4G/4c/4] [G/4d/4] [G/4d/4E,5/4] [G/4d/4] [G/4d/4B,/4] 

[g/2G/4d/4D,/2] [G/4d/4] [^f/2G/4d/4E,3/4] [G/4d/4] [d/2G/4] [G/4e/4E,5/4] [e/2G/4d/4] [G/4c/4B,/4] [B/2G/4D,/2] [G/4A/4] [b13/8E,3/4E/4A/4] [E/4B/4] 

[E/4B/4E,5/4] [E/4B/4] [E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4] [a5/2E/4B/4E,3/4] [E/4c/4] [E/4c/4] [E/4c/4E,5/4] [E/4c/4] [E/4c/4B,/4] [g/8E/4c/4D,/2] 

[a/4z/8] [E/4c/4z/8] g/8 [^f/2G/4d/4] [G/4d/4B,/4] [g/2G/4d/4D,/2] [G/4d/4] [^f/2G/4d/4E,3/4] [G/4d/4] [G/4d/2] [G/4e/4E,5/4] [G/4d/2] [G/4e/4E,5/4] 

[G/4d/2] [G/4c/4B,/4] [B/2G/4D,/2] [G/4A/4] [b13/8E,3/4E/4A/4] [E/4B/4] [E/4B/4] [E/4B/4E,5/4] [E/4B/4] [E/4B/4B,/4] [e/2E/4B/4D,/2] [E/4B/4] 

[a13/8E/4B/4E,3/4] [E/4c/4] [E/4c/4] [E/4c/4E,5/4] [E/4c/4] [E/4c/4E,5/4] [E/4c/4] [E/4c/4B,/4] [E/4c/4D,/2] [E/4c/4] [E,3/4G/4c/4] [G/4d/4] [g/8G/4d/4] 

[a/4z/8] [G/4d/4E,5/4z/8] g/8 [^f/2G/4d/4] [G/4d/4B,/4] [g/2G/4d/4D,/2] [G/4d/4] [G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [d/4G/4D,/2] [c'/4G/4d/4] 

[b17/8E,3/4G/4e/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4d/2] [G/4c/4B,/4] [e/2G/4B/4D,/2] [G/4A/4] [b13/8E,3/4E/4A/4] [E/4B/4] [a5/2E/4B/4E,3/4] [E/4c/4] 

[E/4c/4] [E/4c/4E,5/4] [E/4c/4] [E/4c/4B,/4] [E/4c/4D,/2] [E/4c/4] [E,3/4G/4c/4] [G/4d/4] [g/8G/4d/4] [a/4z/8] [E/4c/4z/8] g/8 [^f/2E,3/4G/4c/4] 

[G/4d/4] [g/2G/4d/4] [G/4d/4E,5/4] [G/4d/4] [G/4d/4B,/4] [G/4d/4D,/2] [G/4d/4] [e7/2C,3/4G/4c/4] [G/4c/4] [G/4c/4C,3/4] [G/4c/4] [G/4c/4G,/4] 

[G/4c/4C,/2] [G/4c/4] [D,3/4G/4d/4] [G/4d/4] [G/4d/4] [G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [d/4G/4D,/2] [c'/4G/4d/4] [b17/8E,3/4G/4e/4] [G/4e/4] 

[G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4b7/8B,/4] 

[G/4e/4g/4D,/2] [G/4e/4] [G/4e/4b/4B,/4] [G/4e/4g/4D,/2] [G/4e/4] [a17/8G/4d/4e/4E,3/4] [G/4d/4b/4] [G/4d/4g/4] [G/4d/4e/4D,3/4] [G/4d/4e/2] 

[G/4d/4b/4A,/4] [^f/4G/4d/4g/4D,/2] [g/4G/4d/4e/4] [d2E,3/4G/4e/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] 

[G/4e/4E,3/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] [b17/8G/4d/4E,3/4] [G/4d/4] [G/4d/4] [G/4d/4E,5/4] [G/4d/4] 

[G/4d/4B,/4] [G/4d/4D,/2] [G/4d/4] [G/4d/4E,3/4] [G/4d/4] [e7/2C,3/4G/4c/4] [G/4c/4] [G/4c/4] [G/4c/4C,3/4] [G/4c/4] [G/4c/4G,/4] [G/4c/4C,/2] [G/4c/4] 

[D,3/4G/4d/4] [G/4d/4] [G/4d/4] [G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [^f/4G/4d/4D,/2] [g/4G/4d/4] [d2E,3/4G/4e/4] [G/4e/4] [G/4e/4b7/8B,/4] 

[G/4e/4g/4D,/2] [G/4e/4] [a17/8G/4d/4E,3/4] [G/4d/4] [G/4d/4] [G/4d/4] [G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [d/4G/4D,/2] [c'/4G/4d/4] [b17/8E,3/4G/4e/4] 

[G/4e/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4b/4B,/4] [G/4e/4g/4D,/2] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] 

[G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] [a17/8G/4d/4E,3/4] [G/4d/4] [G/4d/4] [G/4d/4E,5/4] [G/4d/4] [G/4d/4B,/4] [G/4d/4D,/2] [G/4d/4] [e7/2C,3/4G/4c/4] 

[G/4c/4] [G/4c/4C,3/4] [G/4c/4] [G/4c/4G,/4] [G/4c/4C,/2] [G/4c/4] [D,3/4G/4d/4] [G/4d/4] [G/4d/4] [G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [d/4G/4D,/2] 

[c'/4G/4d/4] [b17/8E,3/4G/4e/4] [G/4e/4] [e3/4C,2z/4] b/4 g/4 e/4 [e3/4z/4] b/4 g/4 e/4 e/4 b/4 g/4 e/4 [a17/8e/4] b/4 g/4 [e7^F,/2] G,/2 E/2 ^F,/2 G,/2 

E/2 [C,4z/2] G,/2 ^F,/2 G,/2 E2 [eE,4z/2] B,/2 [b^F,/2] G,/2 [bD/2] ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [^F,/2z/4] g/8 a/8 

[g/2G,/2] [^f/2D/2] [^f3/4C,4z/2] [G,/2z/4] g/4 [e7^F,/2] G,/2 E/2 ^F,/2 G,/2 [g^F/8] E/4 b/4 [^f/4g/4] [g/4e/4] [G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] 

[b17/8G/4d/4E,3/4] [G/4d/4] [G/4d/4] [e7/2C,3/4G/4c/4] [G/4c/4] [G/4c/4] [G/4c/4G,/4] [G/4c/4C,/2] [G/4c/4] [G/4c/4] [G/4c/4C,3/4] [G/4c/4] 

[G/4c/4C,3/4] [G/4c/4] [G/4c/4G,/4] [G/4c/4C,/2] [G/4c/4] [D,3/4G/4d/4] [G/4d/4] [G/4d/4] [G/4d/4D,3/4] [G/4d/4] [G/4d/4A,/4] [d/4G/4D,/2] [c'/4G/4d/4] 

[b17/8E,3/4G/4e/4] [G/4e/4] [D,3/4G/4d/4e/4] [G/4d/4b/4] [G/4d/4g] [G/4d/4e/4D,3/4] [G/4d/4e/4] [G/4d/4b/4A,/4] [^f/4G/4d/4g/4D,/2] [g/4G/4d/4e/4] 

[d2E,3/4G/4e/4] [G/4e/4] [G/4e/4] [G/4e/4E,5/4] [G/4e/4] [G/4e/4B,/4] [G/4e/4D,/2] [G/4e/4] [G/4e/4E,3/4] [G/4e/4] [a17/8G/4d/4E,3/4] [G/4d/4] [G/4d/4] 

[G/4d/4] [G/4d/4E,5/4] [G/4d/4] [g/2G/4d/4] [G/4d/4E,5/4] [d/4G/4] [c'/4e/4] [b/4E,2e/4] [bz/4] g/4 e/4 [D,e/4] [b7/8z/4] g/4 e/4 e/4 [b7/8z/4] g/4 e/4 

[g/2C,2e/4] b/4 [gz/4] e/4 [D,2e/4] b/4 [gz/4] e/4 [g/2C,2e/4] b/4 [gz/4] e/4 [D,2e/4] b/4 [d/4g/4] [c'/4e/4] [b/4E,3e/4] [bz/4] g/4 e/4 e/4 [b7/8z/4] 

g/4 e/4 [D,2e3/4z/4] b/4 [gz/4] e/4 e/4 [b7/8z/4] g/4 e/4 [g/4E,/2E/2e/2] [^f/4b/4] [e/4E/2g/4E,/2] e/4 [E/2e/2E,/2z/4] b/4 [^f/2g/4] e/4 

[g/4E,/2E/2e/2] [^f/4b/4] [e/4E/2g/4E,/2] e/4 [g/2C,2e/4] b/4 [gz/4] e/4 [D,2e/4] b/4 g/4 e/4 [D,2e3/4z/4] b/4 g/4 e/4 e/4 [b7/8z/4] g/4 ^f/8 [e11/8E/2] 

D/2 [^F,3/2z/2] G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 B,/2 [E,3z/2] B,/2 ^F,/2 G,/2 E/2 [eE,4z/2] B,/2 [E,3z/2] B,/2 ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] 

[g/2B,/2] [a7/4^F,/2] G,/2 D/2 [^F,3/2z/2] G,/2 D/2 [E,4z/2] B,/2 [E,3z/2] B,/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 [bD/2] ^F,/2 [b/2G,/2] [^c/2D/2] 

[d/2E,4] [e/4B,/2] d/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 G,/2 E/2 [eE,4z/2] B,/2 [b^F,/2] G,/2 C/2 [^f/8D/2] g/4 e/4 [e3/4z/4] b/4 [d/4g/4] 

[c'/4e/4] [b/4E,3e/4] [bz/4] g/4 e/4 e/4 b/4 [a/4g/4] [b/4e/4] [a13/8D,2e/4] b/4 [^f/4g/4] [g/4e/4] [d17/8E,3e/4] b/4 g/4 e/4 [e3/4z/4] b/4 g/4 e/4 e/4 

b/4 g/4 e/4 [D,e/4] [b7/8z/4] g/4 e/4 [e3/4z/4] b/4 g/4 e/4 [D,2e3/4z/4] b/4 g/4 e/4 [D,2e3/4z/4] b/4 g/4 e/4 D/8 A,/2 [a^F,E,/2] A,/2 [b3/2G,2z/2] D,/2 

E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] 

B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 [E,5/4z/2] B,/4 D,/2 E,3/4 

[E,5/4z/2] B,/4 D,/2 A,/2 [g/2B,/2] [a2D,2z/2] A,/2 E,/2 ^F,/2 [a/2G,/2] [g/2D/2] [a/2=F,4] [b/4C,/2] a/8 b/8 [a5A,/2] C,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 

G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E2 [eE,4z/2] B,/2 [b^F,/2] G,/2 E/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 D/2 [^F,3/2z/2] G,/2 D/2 [E,4z/2] B,/2 

^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 B,/2 [b^F,/2] G,/2 [bD/2] ^F,/2 [b/2G,/2] [^c/2D/2] [d/2E,4] 

[e/4B,/2] d/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [^F,3/2z/2] G,/2 D/2 [E,4z/2] B,/2 

^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 [^f/8D/2]


Here is the updated code:





public class MarkovMusic
    {
        /// <summary>
        /// dictionary of token associations
        /// </summary>
        private Dictionary<String, List<String>> mc_MarkovTokens = new Dictionary<String, List<String>>();

        /// <summary>
        /// basic random number generator
        /// </summary>
        private Random mc_Rand = new Random();

        //up to how many follow-on tokens we want to associate per token
        //NOTE - doesnt always guarantee 4, i've seen 3 and 2 (likely a whitespace cleanup error somewhere)
        private int n = 3;

        /// <summary>
        /// basic constructor
        /// </summary>
        public MarkovMusic() { }

        /// <summary>
        /// parses a file into a markov token dictionary
        /// </summary>
        /// <param name="fileName"></param>
        public void parse(String fileName)
        {

            //get access to the input file
            FileStream fs = new FileStream(fileName, FileMode.Open);

            //setup the stream reader
            StreamReader sr = new StreamReader(fs);

            //holds tokens in work
            String[] tokenArray;

            //the text following the token
            String followingText = "";

            //create a word token placeholder for carryovers
            String tokenCarry = "";

            //keep reading the file until you reach the end
            while (!sr.EndOfStream)
            {
                //check to see if a token was carried over from last loop
                if (tokenCarry == "")
                {
                    //replace token carry with the line
                    tokenCarry = sr.ReadLine();
                }
                else
                {
                    //combine with tokenCarry 
                    tokenCarry += " " + sr.ReadLine();
                }

                //split tokenCarry into tokens
                tokenArray = tokenCarry.Trim().Split(' ');

                //create tokens and store them into the markov Token Dictionary
                for (int i = 0; i < tokenArray.Length; i++)
                {
                    //ensure you have room to define a new token entry
                    //[t1][t2 t3 - tn]<[EOL]
                    if ((i + n) < (tokenArray.Length - 1))
                    {
                        //calculate the following text
                        for (int j = 1; j <= n; j++)
                        {
                            followingText += tokenArray[i + j].Trim() + " ";
                        }

                        //check to see if the token already exists
                        if (mc_MarkovTokens.ContainsKey(tokenArray[i].ToLower()))
                        {
                            //it does exist, so add the new token association
                            mc_MarkovTokens[tokenArray[i].Trim().ToLower()].Add(followingText.Trim());
                        }
                        else
                        {
                            //it doesnt exist, so create it and its token association
                            mc_MarkovTokens.Add(tokenArray[i].Trim().ToLower(), new List<String>());
                            mc_MarkovTokens[tokenArray[i].Trim().ToLower()].Add(followingText.Trim());
                        }
                        //reset following text
                        followingText = "";
                    }
                    else
                    {
                        //calculate the following text
                        for (int j = i; j < tokenArray.Length; j++)
                        {
                            followingText += tokenArray[j].Trim() + " ";
                        }

                        //carry over the training text to the next iteration
                        tokenCarry = followingText.Trim();
                        followingText = "";
                        break;
                    }
                }
            }

            //it is likely we missed tokens near the end, do the same thing as we did above, but for them
            if (tokenCarry.Length > 0)
            {
                tokenArray = tokenCarry.Trim().Split(' ');

                //calculate the following text
                for (int j = 1; j < tokenArray.Length; j++)
                {
                    followingText += tokenArray[j].Trim() + " ";
                }

                if (mc_MarkovTokens.ContainsKey(tokenArray[0].ToLower()))
                {
                    mc_MarkovTokens[tokenArray[0].Trim().ToLower()].Add(followingText.Trim());
                }
                else
                {
                    mc_MarkovTokens.Add(tokenArray[0].Trim().ToLower(), new List<String>());
                    mc_MarkovTokens[tokenArray[0].Trim().ToLower()].Add(followingText.Trim());
                }
            }

            //close the streams
            sr.Close();
            fs.Close();
        }

        /// <summary>
        /// outputs a file with generated text based on the current token association dictionary
        /// </summary>
        /// <param name="fileName"></param>
        public void output(String fileName)
        {
            //get the first key
            String token = mc_MarkovTokens.Keys.ToList<String>()[0];
            List<String> tokens;
            String output = token;
            int index = 0;

            //get 1000 token pairs
            for (int i = 0; i < 1000; i++)
            {
                if (mc_MarkovTokens.ContainsKey(token.ToLower()))
                {
                    //find the first token
                    tokens = mc_MarkovTokens[token.ToLower()];
                }
                else
                {
                    break;
                }

                //select a random index within its associated list 
                //(this also keeps us within distribution parameters since i allow duplicate entries)
                index = mc_Rand.Next(0, tokens.Count - 1);

                //get the last word from the associated list, make it the next token
                token = tokens[index].Split(' ')[tokens[index].Split(' ').Length - 1];

                //append the chosen token list to the output string
                output += " " + tokens[index];
            }

            //create a file access stream
            FileStream fs = new FileStream(fileName, FileMode.Create);
            //create a stream writier to write the string
            StreamWriter sw = new StreamWriter(fs);

            //write the file
            sw.Write(output);

            //close the streams
            sw.Close();
            fs.Close();
        }


    }


As you can see, it bears almost exact resemblance to the other code, just a bit of tweaking on the output to watch for unknown tokens and to ensure the first token chosen always exists within the dictionary keys






#8 Didge   Members   -  Reputation: 100

Like
0Likes
Like

Posted 09 August 2011 - 02:22 PM

Cool stuff!

I've been wondering how to implement something like this, and your code pretty much says it all.

I tried to convert your ABC score to a MIDI file using abc2midi, but it fails to read your ABC syntax. How do you convert your ABC to MIDI to listen to it or do you have some ABC music player?

BR
Emil

#9 willh   Members   -  Reputation: 160

Like
0Likes
Like

Posted 09 August 2011 - 03:35 PM

Great work Net Genome! Thanks for sharing your results from start to finish. :)

Any chance you could post a YouTube video showing an input song and an output song?

#10 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 09 August 2011 - 05:37 PM

Best thing I can recommend (there is rather a derth of good ABC players) is to goto: http://sites.google.com/site/lotroabc/ and get the ABC Navigator- Editor/Player

you may need to reduce the number of notes per line in order to get my example to work, here is a shortened version i truncated a bit to make it work.


X:1
 T:Chrono Cross - Timescar (Special gamedev.net truncated version <img src='http://public.gamedev.net/public/style_emoticons/<#EMO_DIR#>/ohmy.gif' class='bbc_emoticon' alt=':o' />) )
L:1/4
Q:110
K:C
[e,4z/2] B,/2 ^F,/2 G,/2 ^F,/2 G,/2 E/2 ^F,/2 G,/2 E/2 [C,4z/2]
 G,/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 [bD/2] 
^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 ^F,
/2 G,/2 D/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E/2 [C,4z/2] G,
/2 ^F,/2 G,/2 C,/2 A,/2 C,/2 A,/2 C,/2 [aG,/2] C,/2 [bA,/2] C,
/2 [F,4z/2] C,/2 A,/2 C,/2 [aG,/2] C,/2 [F,4z/2] C,/2 A,/2 E,
/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2
 C/2 [d/2G,/2] [b11/8G,2z/2] D,/2 A,/2 [b3/2G,2z/2] D,/2 A,/2 E,
/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2
 C/2 [d/2G,/2] [b11/8G,2z/2] D,/2 A,/2 [g/2B,/2] [a2D,2z/2] A,/2 E,
/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2
 C/2 [d/2G,/2] [b11/8G,2z/2] D,/2 A,/2 E,/2 ^F,/2 [g11/8C,4z/2] G,
/2 C/2 [^f/8D/2] g/4 ^f/8 [e11/8E/2] D/2 C/2 [d/2G,/2] [b11/8G,2z/2]
 D,/2 A,/2 E,/2 ^F,/2 [g11/8C,4z/2] G,/2 C/2 [^f/8D/2] g/4 ^f/8 
[e11/8E/2] D/2 C/2 [d/2G,/2] [b11/8G,2z/2] D,/2 A,/2 C,/2 [F,4z/2]
 C,/2 A,/2 C,/2 [F,4z/2] C,/2 A,/2 C,/2 A,/2 C,/2 [F,4z/2] C,
/2 A,/2 C,/2 [F,4z/2] C,/2 A,/2 C,/2 [F,4z/2] C,/2 [aG,/2] C,
/2 [bA,/2] C,/2 [g/2E,4] [^f/2B,/2] [g5^F,/2] G,/2 D/2 [E,4z/2] B,
/2 ^F,/2 [b/2G,/2] [^c/2D/2] [d/2E,4] [e/4B,/2] d/8 e/8 [d/2^F,/2]
 [^c/2G,/2] [bD/2] ^F,/2 [b/2G,/2] [^c/2D/2] [d/2E,4] [e/4B,/2] d/8
 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4]
 [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E/2 [eE,4z/2]
 B,/2 [b^F,/2] G,/2 [g^F/8] E/4 D/8 e/8 [d/2^F,/2] [^c/2G,/2] 
[bD/2] ^F,/2 [a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,
/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E/2 ^F,/2 G,/2 E/2 [C,4z/2] G,/2
 ^F,/2 G,/2 D/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 C,/2 A,/2 C,/2 A,/2 C,/2
 [aG,/2] C,/2 [bA,/2] C,/2 G,/2 C,/2 A,/2 [b3/2G,2z/2] D,/2 A,
/2 [a^F,E,/2] A,/2 [b3/2G,2z/2] D,/2 A,/2 [g/2B,/2] [a7/4^F,/2] G,
/2 D/2 ^F,/2 G,/2 D/2 [^F,/2z/4] g/8 a/8 [g/2G,/2] [^f/2D/2] 
[^f3/4C,4z/2] [G,/2z/4] g/4 [e7^F,/2] G,/2 E/2 ^F,/2 [a/2G,/2]
 [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [^F,3/2z/2] G,
/2 D/2 [E,4z/2] B,/2 [b^F,/2] G,/2 [bD/2] ^F,/2 [a/2G,/2] [g/2D/2]
 [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [^F,/2z/4] g/8 a/8 
[g/2G,/2] [^f/2D/2] [^f3/4C,4z/2] [G,/2z/4] g/4 [e7^F,/2] G,/2 E/2
 ^F,/2 G,/2 D/2 ^F,/2 G,/2 C/2 [^f/8D/2] g/4 [e7^F,/2] G,/2 E/2 
[C,4z/2] G,/2 ^F,/2 G,/2 E/2 ^F,/2 G,/2 B,/2 [E,3z/2] B,/2 ^F,/2
 G,/2 E/2 ^F,/2 G,/2 D/2 [E,4z/2] B,/2 ^F,/2 G,/2 E2 [eE,4z/2] B,
/2 [b^F,/2] G,/2 [bD/2] ^F,/2 [b/2G,/2] [^c/2D/2] [d/2E,4] [e/4B,/2]
 d/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 [b/2G,/2] [^c/2D/2]
 [d/2E,4] [e/4B,/2] d/8 e/8 [d/2^F,/2] [^c/2G,/2] [bD/2] ^F,/2 
[a/2G,/2] [g/2D/2] [^f/2E,4] [g/2B,/2] [a7/4^F,/2] G,/2 D/2 [E,4z/2]
 B,/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 D/2 ^F,/2 G,/2 E/2 
[C,4z/2] G,/2 ^F,/2 G,/2 


I would post a youtube video, but i dont have a full version of Fraps, so i'm limited to 30s clips.

#11 willh   Members   -  Reputation: 160

Like
0Likes
Like

Posted 09 August 2011 - 07:07 PM

Best thing I can recommend (there is rather a derth of good ABC players) is to goto: http://sites.google.com/site/lotroabc/ and get the ABC Navigator- Editor/Player

you may need to reduce the number of notes per line in order to get my example to work, here is a shortened version i truncated a bit to make it work.



Just downloaded it. There were some errors importing the song (I don't think it likes the first [e,.....]).

The tune starts... and then gets surprisingly good!! Very impressive.


Will

#12 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 09 August 2011 - 08:02 PM


Best thing I can recommend (there is rather a derth of good ABC players) is to goto: http://sites.google.com/site/lotroabc/ and get the ABC Navigator- Editor/Player

you may need to reduce the number of notes per line in order to get my example to work, here is a shortened version i truncated a bit to make it work.



Just downloaded it. There were some errors importing the song (I don't think it likes the first [e,.....]).

The tune starts... and then gets surprisingly good!! Very impressive.


Will


yea, i ran into a similar issue, but you can make it attempt to play it anyway if you click the name of the song in the central grey area after you load it... it seems to play ok after that even without the truncation from my previous entry.

The best ABC player by far is LoTRO itself. The devs whom designed that have done the best version of an ABC player I've seen. If you have LoTRO, load it up and play the files for a -much- better experience. Even with the 30s limit, i'll try posting a Fraps grab.

#13 Net Gnome   Members   -  Reputation: 769

Like
5Likes
Like

Posted 10 August 2011 - 02:53 AM

Here is the first 30s of the generated song:

#14 willh   Members   -  Reputation: 160

Like
0Likes
Like

Posted 10 August 2011 - 02:23 PM

Here is the first 30s of the generated song: http://www.youtube.c...h?v=q8m73PKdYik



Well done!! Fantastic! Documented from start to finish-- you deserve some sort of GameDev poster of the month award. :D

I've used markov chains before to build a chatbot, using scripts from movies as the input. The results, while proper english, were less than stellar. Longer scripts got more attention, so the conversation would tend to get stuck in one movie ('How to get ahead in advertising' seemed the computers favorite).

Have you tried importing multiple songs in to a single markov chain? You could get more variety that way.

#15 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 10 August 2011 - 03:48 PM


Here is the first 30s of the generated song: http://www.youtube.c...h?v=q8m73PKdYik



Well done!! Fantastic! Documented from start to finish-- you deserve some sort of GameDev poster of the month award. :D

I've used markov chains before to build a chatbot, using scripts from movies as the input. The results, while proper english, were less than stellar. Longer scripts got more attention, so the conversation would tend to get stuck in one movie ('How to get ahead in advertising' seemed the computers favorite).

Have you tried importing multiple songs in to a single markov chain? You could get more variety that way.


yep, just posted a vid with 5 samples instead of one. More variety, but it muddles itself a few times... the next phase may help smooth out those hiccups.






#16 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 12 August 2011 - 11:54 AM

Anyway, havent completed the next code set yet, but i wanted to post a bit on the conceptual thought behind the next iteration.

Currently the tokens are setup in a 1:n associative relationship inside a token dictionary so if you had a 1:3 associative relationship, the first 3 token entries may look like this:

[T1][T2 T3 T4]
[T2][T3 T4 T5]
[T3][T4 T5 T6]

By increasing or decreasing the token association ratio you can control aspects of the output. Essentially how well the music flows from one note to the next, because it builds based on the parsed note history and selects via weighted distribution (because of allowed duplicate entries). However, with larger token ratios, the music becomes more like the original, because there are less probable variations stored in the token dictionary and if the token ratio is small, the music becomes more random due to too many possibilities. What is needed, is a way to select or weight the choice further towards a "desired" distribution, but maintain a randomized selection process to allow for variation. Tiered token associations may resolve this.

Tiered Token associations is almost exatly what it sounds like. Multiple varying associative ratio token dictionaries, such as 1:5, 1:4, 1:3, and 1:2 fit together in a hierarchial fashion from largest ratio (1:5) to lowest (1:2) with the higher tiers weighting allowable distributions on the next lower in the hierarchy. Since the larger ratios capture more realistic music patters, they are more suited for a "strategic" or "thematic" note selection that becomes more "tactical" or "fleeting" as you descend the hierarchy.

ex) 3-tier heirarchy

tier 3 seed chosen: [t2]
[t2] associates to [t2 t3 t4 t1],[t2 t1 t3 t4],[t1 t2 t3 t4]
randomly select theme to be [t1 t2 t3 t4]

tier 2 seed chosen: [t1]
[t1] associates to [t2 t3 t4],[t3 t4 t2],[t2 t4 t3],[t3 t2 t4], [t4, t3, t2]
since tier 3 was [t1 t2 t3 t4], we want to end in a [t2] token so we downselect to [t3 t4 t2],[t4, t3, t2]
randomly select theme to be [t4, t3, t2]

tier 1 seed inital becomes [t4]
[t4] associates to [t2 t3],[t3 t2],[t3 t4],[t1 t3]
since tier 2 was [t4, t3, t2] we want to end in a [t3], so we downselect to [t2 t3],[t1 t3]
randomly select [t1 t3] as the note run as downselecting 1:1 ratio will produce the same result (unless you store additional note information in your tokens like octaves and allow for octave variation thresholds).

You would continue to recurse through the hierarchy until you reach you final desired tier 3 token count.

What if a run association isnt found? Then you build on your current token tier until you encounter a matching end token that completes the theme selection and then move on from there.

So that is the basis I'm thinking of, and it should produce a better result than my last video, but how much better remains to be seen. Hopefully I can get this coded this weekend, but we'll see.





#17 willh   Members   -  Reputation: 160

Like
0Likes
Like

Posted 12 August 2011 - 05:35 PM

What if a run association isnt found? Then you build on your current token tier until you encounter a matching end token that completes the theme selection and then move on from there.

So that is the basis I'm thinking of, and it should produce a better result than my last video, but how much better remains to be seen. Hopefully I can get this coded this weekend, but we'll see.


I think you're at the point where the markov-chain isn't going to be enough on its own. I'm not a musician, but I would think you'll need to check the 'sanity' of the following:

1. Does the addition of sequence [t2 t3 t4 ... tN] stay in key?
2. Does the addition cause the sequence to lose tempo?
3. Does the sequence belong to the apporpriate section of the song? (most songs have middles, endings, etc..)

So, rather than just randomly pick from the chain, you need to pick, check, and if failed, pick a new one.

Something that would be really nice would be multi-voice compositions. So a melody voice and a bass voice for example. Basically each value in the markov chain would contain 2 values-- one for each voice. Could be tricky due to tempo/timing though...

#18 Net Gnome   Members   -  Reputation: 769

Like
1Likes
Like

Posted 12 August 2011 - 08:19 PM

The concept worked! This version produced the best ones yet. Some of the ones it comes up with are good enough to use as decent game music.

Anyway, here are two vids I put together showing some of the results:

A slow tune:
http://www.youtube.c...h?v=YJe05wQglDw

A fast tune:
http://www.youtube.c...h?v=mohqgYDZL_g

So you can see (and hear) that the tiered concept worked very well.

Here is one of the songs, the slow one i think (i overwrote the fast one =/ )

X:1
T:PROCEDURAL GENERATED TUNE
L:1/4
Q:90
K:C
[G7/8z/4] [C3/4z3/8] F3/8 [G,23/8z3/4] [D11/8z3/8][A9/8z3/8] F/4 D3/8 [^D,3/2z3/4] [^A,11/8z3/8]c'3/8 [C11/8z/4] [F3/2^a/2z3/8] [F,3/2z3/8] [G=a/2z3/8][c'3/8z/4] [C3/2z3/8] [F3/2^a/2z3/8] [F,11/8z3/8] [G15/8=a/2z3/8][G15/8=a/2z3/8] [C5/8z3/8] F/4 [C,23/8c'z3/4] [C11/8z3/8][G7/8=a/2z3/8] [C5/8z/4] F3/8 [G,23/8a3/4] [D11/8g3/8][G7/8=a3/8z/4] [C3/4z3/8] F3/8 [G,23/8a3/4] [D11/8g3/8]A3/8 F3/8 D/4 [^D,3/2z3/4] [^A,11/8z3/8][c'/2z3/8] [C11/8z3/8] [F11/8^a3/8z/4] [F,3/2z3/8] [G=a/2z3/8][Gz3/8] [C5/8z3/8] F/4 [=D,3/2dz3/4] [C3/2z3/8][G7/8z3/8] [C5/8z/4] F3/8 [G,23/8dz3/4] [Fz3/8][G7/8z3/8] [C5/8z/4] F3/8 [=D,3/2dz3/4] [C11/8z3/8]A3/8 F3/8 D/4 [^D,3/2z3/4] [^A,11/8z3/8]A/4 F3/8 D3/8 [^D,11/8z3/4] [^A,11/8z3/8][Gz3/8] [C5/8z3/8] F/4 [G,2z3/8] [D3/2z3/8][G7/8=a/2z3/8] [C5/8z3/8] F/4 [G,23/8a3/4] [D11/8g3/8][G15/8=a3/8] [C5/8z/4] F3/8 [C,23/8c'z3/4] [C11/8z3/8][G7/8=a3/8z/4] [C3/4z3/8] F3/8 [G,23/8a3/4] [D11/8g3/8][G7/8z3/8] [C5/8=a5/8z3/8] F/4 [C,23/8g11/2z3/8] [C15/8z3/8][C15/8z3/8] [F11/8z/4] [^A3/4z3/8] [F,3/2z3/8] [=Az3/8][F3/4z3/8] [^A7/8z3/8] [G7/8z/4] [F,3/2z3/8] [c2z3/8]G/4 [=Az3/8] [F3/2z3/8] [F,3/2z3/8] c3/8[F11/8z3/8] [F,11/8z3/8] [G7/8z/4] [C3/4z3/8] F3/8[F11/8z3/8] [F,11/8z/4] [Gz3/8] [C3/4z3/8] F3/8[F11/8z3/8] [F,11/8z/4] [G3/2z3/8] [C3/2z3/8] [F/2z3/8][F11/8z/4] [F,3/2z3/8] [Gz3/8] [C3/4z3/8] F3/8[^D11/8gz3/4] [G11/8z/4] [C3/4=d2z3/8] ^D3/8 [=D,3/2z3/8][F11/8z3/8] [F,11/8c'5/8z3/8] [G7/8z/4] [C3/4^a3/8] [F3/8=a3][F11/8^a/2z3/8] [F,11/8z3/8] [G7/8=a3/8z/4] [C3/4z3/8] F3/8[F11/8=d3/8] [F,11/8z/4] [G2c'/2z3/8] [C3/4z3/8] F3/8[F11/8^a3/8z/4] [F,3/2z3/8] [G=a/2z3/8] [C3/4z3/8] F3/8[F11/8a7/8z3/8] [D,11/8z/4] [Gz3/8] [C3/4a3/4z3/8] F3/8[F11/8z3/8] [F,11/8z/4] [Gz3/8] [C3/4z3/8] F3/8[F11/8z/4] [F,3/2z3/8] [Gz3/8] [C3/4z3/8] F3/8[^D11/8g7/8z5/8] [G3/2z3/8] [C3/4=d15/8z3/8] ^D3/8 [=D,11/8z3/8][F11/8^a/2z3/8] [F,11/8z3/8] [G7/8=a3/8z/4] [C3/4z3/8] F3/8[F11/8z3/8] [F,11/8c'5/8z/4] [Gz3/8] [C3/4^a3/8] [F3/8=a3][F11/8f11/8z/4] [F,3/2z3/8] [Gz3/8] [C3/4z3/8] F3/8[F11/8z3/8] [F,11/8z3/8] [G7/8z/4] [C3/4z3/8] F3/8[F3/2z3/8] [^D,11/8z3/8] [G15/8z3/8] [^A,5/8z3/8] [F11/8z/4][F11/8z3/8] [^D,11/8z3/8] [G15/8z3/8] [^A,5/8z/4] [F3/2z3/8][F3/2z3/8] [F,3/2z3/4] [C11/8z3/8] [F11/8z3/8] [F,11/8z/4][F3/2z3/8] [^D,11/8^a3/8] [G15/8c'3/8] [^A,5/8d3/8] [F11/8c'11/8z/4][=F11/8f3/8] [^D,11/8d3/8] [G15/8^d5/8z3/8] [^A,5/8z/4] [F3/2f/2z3/8][F3/2d3/2z3/8] [^D,11/8z3/8] [G15/8z3/8] [^A,5/8z3/8] [F11/8z/4][F11/8^az3/8] [^D,11/8z3/8] [G15/8z3/8] [^A,5/8d/4] [F3/2c'25/8z3/8][F11/8z3/8] [^D,11/8z3/8] [G15/8z3/8] [^A,5/8z/4] [F3/2z/4][F3/2f3/8] [^D,3/2d3/8] [G15/8^d3/4z3/8] [^A,3/4z3/8] [F11/8f3/8][=F3/2f3/8] [^D,11/8d3/8] [G15/8^d3/4z3/8] [^A,5/8z3/8] [F11/8f3/8z/4][F11/8z3/8] [^D,11/8^a3/8] [G15/8c'3/8] [^A,5/8d/4] [F3/2c'3/2z3/8][F11/8z3/8] [^A7/8z3/8] [d11/8z3/8] [^A,7/8z/4] ^A3/8[F,3/2z3/8] [c'/2z3/8] [C11/8z3/8] [F11/8^a/2z3/8] [F,11/8z3/8][G7/8z/4] [C3/4z3/8] F3/8 [G,23/8z3/4] [D11/8z3/8][A9/8z3/8] F/4 D3/8 [^D,3/2z3/4] [^A,11/8z3/8]c'3/8 [C11/8z/4] [F3/2^a/2z3/8] [F,3/2z3/8] [G=a/2z3/8][c'3/8z/4] [C3/2z3/8] [F3/2^a/2z3/8] [F,11/8z3/8] [G15/8=a/2z3/8][G15/8=a/2z3/8] [C5/8z3/8] F/4 [C,23/8c'z3/4] [C11/8z3/8][G7/8=a/2z3/8] [C5/8z/4] F3/8 [G,23/8a3/4] [D11/8g3/8][G7/8=a3/8z/4] [C3/4z3/8] F3/8 [G,23/8a3/4] [D11/8g3/8]A3/8 F3/8 D/4 [^D,3/2z3/4] [^A,11/8z3/8][c'/2z3/8] [C11/8z3/8] [F11/8^a3/8z/4] [F,3/2z3/8] [G=a/2z3/8][Gz3/8] [C5/8z3/8] F/4 [=D,3/2dz3/4] [C3/2z3/8][G7/8z3/8] [C5/8z/4] F3/8 [G,23/8dz3/4] [Fz3/8][G7/8z3/8] [C5/8z/4] F3/8 [=D,3/2dz3/4] [C11/8z3/8]A3/8 F3/8 D/4 [^D,3/2z3/4] [^A,11/8z3/8]A/4 F3/8 D3/8 [^D,11/8z3/4] [^A,11/8z3/8][Gz3/8] [C5/8z3/8] F/4 [G,2z3/8] [D3/2z3/8][G7/8=a/2z3/8] [C5/8z3/8] F/4 [G,23/8a3/4] [D11/8g3/8][G15/8=a3/8] [C5/8z/4] F3/8 [C,23/8c'z3/4] [C11/8z3/8][G7/8=a3/8z/4] [C3/4z3/8] F3/8 [G,23/8a3/4] [D11/8g3/8][G7/8z3/8] [C5/8=a5/8z3/8] F/4 [C,23/8g11/2z3/8] [C15/8z3/8][C15/8z3/8] [F11/8z/4] [^A3/4z3/8] [F,3/2z3/8] [=Az3/8][F3/4z3/8] [^A7/8z3/8] [G7/8z/4] [F,3/2z3/8] [c2z3/8]G/4 [=Az3/8] [F3/2z3/8] [F,3/2z3/8] c3/8[F11/8z3/8] [^D,11/8z3/8] [G15/8z/4] [^A,3/2z3/8] [F3/2z3/8][F11/8z3/8] [^D,11/8z/4] [G2z3/8] [^A,3/4z3/8] [F3/2z3/8][F11/8f3/8] [^D,11/8d/4] [G2^d3/4z3/8] [^A,3/4z3/8] [F11/8f/2z3/8][F11/8f/4] [^D,3/2d3/8] [G15/8^d3/4z3/8] [^A,3/4z3/8] [F11/8f/2z3/8][F11/8f3/8] [^D,11/8d3/8] [G15/8^d5/8z/4] [^A,3/4z3/8] [F3/2f/2z3/8][=F11/8f3/8] [^D,11/8d3/8] [G15/8^d5/8z/4] [^A,3/4z3/8] [F3/2f/2z3/8][F11/8z3/8] [^D,11/8^a/4] [G2c'3/8] [^A,3/4d3/8] [F11/8c'11/8z3/8][=F11/8d11/8z/4] [^D,3/2z3/8] [G15/8z3/8] [^A,3/4z3/8] [F11/8z3/8][F11/8z3/8] [^D,11/8z3/8] [G15/8z/4] [^A,3/4z3/8] [F3/2z3/8][F11/8z3/8] [^D,11/8z/4] [G2z3/8] [^A,3/4z3/8] [F11/8z3/8][F11/8z/4] [^D,3/2z3/8] [G15/8z3/8] [^A,3/4z3/8] [F11/8z3/8][=F11/8d11/8z3/8] [^D,11/8z3/8] [G15/8z/4] [^A,3/4z3/8] [F3/2z3/8][F11/8d11/8z3/8] [^D,11/8z/4] [G2z3/8] [^A,3/4z3/8] [F11/8z3/8][F,11/8z3/8] [G7/8z/4] [C3/4z3/8] F3/8 [G,23/8z3/4][^D,11/8z3/8] [G15/8z3/8] [^A,5/8z/4] [F3/2z3/8] [F,3/2z3/4][F,11/8z/4] [Gz3/8] [C3/4z3/8] F3/8 [C,23/8z3/8][G5/8z/4] [c23/8z3/8] G3/8 F3/8 C3/8[^D,11/8z3/8] [G15/8z/4] [^A,3/2z3/8] [F3/2z3/8] [F,3/2z3/4][F,11/8z/4] [G3/2z3/8] [C3/2z3/8] [F/2z3/8] [G,23/8z3/4][^D,11/8z/4] [G2z3/8] [^A,3/4z3/8] [F3/2z3/8] [F,11/8z3/4][F,11/8c'5/8z3/8] [G7/8z/4] [C3/4^a3/8] [F3/8=a3] [=D,15/8z3/4][^D,11/8^a/4] [G2c'3/8] [^A,3/4d3/8] [F11/8c'11/8z3/8] [F,11/8z3/4][F,11/8c'7/8z5/8] [C3/2z3/8] [F3/2f3/2z3/8] [F,11/8z3/8] [Gz3/8][F,11/8c'7/8z5/8] [C3/2z3/8] [F11/8f11/8z3/8] [F,11/8z3/8] [G7/8z3/8][^D,11/8z3/8] [G15/8z3/8] [^A,5/8z/4] [F3/2z/4] [^a23/8z/8][F,11/8z/4] [Gz3/8] [C3/4z3/8] F3/8 [G,23/8z3/4][^D,11/8z3/8] [G15/8z/4] [^A,3/4z3/8] [F3/2z3/8] [F,11/8z3/4][^D,11/8z/4] [G2z3/8] [^A,3/4z3/8] [F11/8z3/8] [F,11/8z3/4][F,11/8z3/4] [C11/8z/4] [F3/2z3/8] [F,3/2z3/8] [Gz3/8][F,11/8z5/8] [C3/2z3/8] [F3/2z3/8] [F,11/8z3/8] [Gz3/8][^D,11/8^a3/8] [G15/8c'3/8] [^A,5/8d/4] [F3/2c'3/2z3/8] [F,3/2z3/4][F,11/8c'5/8z/4] [Gz3/8] [C3/4^a3/8] [F3/8=a3] [=D,15/8z3/8][F,11/8c'z3/4] [C11/8z/4] [F3/2f3/2z3/8] [F,3/2z3/8] [Gz3/8][F,11/8c'7/8z5/8] [C3/2z3/8] [F3/2f3/2z3/8] [F,3/2z3/8] [Gz3/8][F,11/8c'7/8z5/8] [C3/2z3/8] [F3/2^az3/8] [F,11/8z3/8] [G7/8z3/8][G3/4z3/8] [c23/8z3/8] G/4 F3/8 C3/8[^A7/8z3/8] [d11/8z3/8] [^A,7/8z/4] ^A3/8 [F3/4z3/8][^A7/8z/4] [d15/8z3/8] [^A,z3/8] ^A3/8 [F3/4z3/8][F,11/8z3/8] c3/8 A/4 F3/8 [G,3/2^a33/8f43/8z3/8][^A3/4z3/8] [F,11/8=a11/8z3/8] [=A7/8z/4] F3/8 C3/8[F,11/8c/4] c3/8 [A3/4z3/8] F3/8 [G,11/8d^a11/2=a23/8z3/8][^A5/8z/4] [F,3/2z3/8] [=Az3/8] F3/8 C3/8[F,11/8z3/8] [G7/8z/4] [C3/4z3/8] F3/8 [G,23/8z3/4][^D,11/8z3/8] [G15/8z3/8] [^A,5/8z/4] [F3/2z3/8] [F,3/2z3/4][C3/4z3/8] F3/8 [C,23/8z3/8] [C15/8z3/8] [F11/8z3/8][C3/4z3/8] F3/8 [G,11/2z5/8] [D15/8z3/8] [Gz3/4][C3/4^a3/8] [F3/8=a3] [=D,15/8z5/8] [C3/2z3/8] [G11/8z3/4][C5/8z3/8] F/4 [=D,3/2dz3/4] [C3/2z3/8] [F11/8az3/8][C3/4a3/4z3/8] F3/8 [G,11/8^a3/4z3/8] [^A7/8z3/8] [D5/8=a5/8z/4][C3/4z3/8] F3/8 [G,23/8z3/4] [D11/8z/4] [d2z3/4][C3/4z3/8] F3/8 [C,11/4z3/8] [C15/8z/4] [F3/2z3/8][C3/4z3/8] F3/8 [G,11/4z5/8] [D3/2z3/8] [d15/8z3/4][C5/8z3/8] F/4 [G,2z3/8] [D3/2z3/8] [G3/2z3/8][C3/4^a3/8] [F3/8=a3] [=D,15/8z3/8] C3/8 [C11/8G/4][C3/4z3/8] F3/8 [=D,11/8d7/8z5/8] [C3/2z3/8] [F3/2az3/8][C3/4a3/4z3/8] F3/8 [G,11/8^a5/8z/4] [^Az3/8] [D3/4=a3/4z3/8][C3/4z3/8] F3/8 [G,11/4d7/8z/4] [F3/4z3/8] [^A3/4D3/2z3/8][C3/4z3/8] F3/8 [=D,11/8d7/8z5/8] [C3/2z3/8] [F11/8az3/8][F,3/2z3/8] [c15/8z3/8] [F3/4z3/8] [^A7/8z3/8] [F,11/8=a11/4z/4][F,11/8c3/8] [c15/8z3/8] [F/4^A/4] [^AF3/8z/4] [^a23/8c'23/8f23/8z/8]



Here is the updated code:

public class MarkovMusic
    {
        /// <summary>
        /// basic random number generator
        /// </summary>
        private Random mm_Rand = new Random();

        /// <summary>
        /// basic constructor
        /// </summary>
        public MarkovMusic() { }

        /// <summary>
        /// parses a file into a markov token dictionary
        /// </summary>
        /// <param name="fileName">file to parse</param>
        /// <param name="n">token association size</param>
        /// <param name="mm_MarkovTokens">token dictionary</param>
        public void parse(String fileName, int n, Dictionary<String, List<String>> mm_MarkovTokens)
        {

            //get access to the input file
            FileStream fs = new FileStream(fileName, FileMode.Open);

            //setup the stream reader
            StreamReader sr = new StreamReader(fs);

            //holds tokens in work
            String[] tokenArray;

            //the text following the token
            String followingText = "";

            //create a word token placeholder for carryovers
            String tokenCarry = "";

            //keep reading the file until you reach the end
            while (!sr.EndOfStream)
            {
                //check to see if a token was carried over from last loop
                if (tokenCarry == "")
                {
                    //replace token carry with the line
                    tokenCarry = sr.ReadLine();
                }
                else
                {
                    //combine with tokenCarry 
                    tokenCarry += " " + sr.ReadLine();
                }

                //split tokenCarry into tokens
                tokenArray = tokenCarry.Trim().Split(' ');

                //create tokens and store them into the markov Token Dictionary
                for (int i = 0; i < tokenArray.Length; i++)
                {
                    //ensure you have room to define a new token entry
                    //[t1][t2 t3 - tn]<[EOL]
                    if ((i + n) < (tokenArray.Length - 1))
                    {
                        //calculate the following text
                        for (int j = 1; j <= n; j++)
                        {
                            followingText += tokenArray[i + j].Trim() + " ";
                        }

                        //check to see if the token already exists
                        if (mm_MarkovTokens.ContainsKey(tokenArray[i].ToLower()))
                        {
                            //it does exist, so add the new token association
                            mm_MarkovTokens[tokenArray[i].Trim().ToLower()].Add(followingText.Trim());
                        }
                        else
                        {
                            //it doesnt exist, so create it and its token association
                            mm_MarkovTokens.Add(tokenArray[i].Trim().ToLower(), new List<String>());
                            mm_MarkovTokens[tokenArray[i].Trim().ToLower()].Add(followingText.Trim());
                        }
                        //reset following text
                        followingText = "";
                    }
                    else
                    {
                        //calculate the following text
                        for (int j = i; j < tokenArray.Length; j++)
                        {
                            followingText += tokenArray[j].Trim() + " ";
                        }

                        //carry over the training text to the next iteration
                        tokenCarry = followingText.Trim();
                        followingText = "";
                        break;
                    }
                }
            }

            //it is likely we missed tokens near the end, do the same thing as we did above, but for them
            if (tokenCarry.Length > 0)
            {
                tokenArray = tokenCarry.Trim().Split(' ');

                //calculate the following text
                for (int j = 1; j < tokenArray.Length; j++)
                {
                    followingText += tokenArray[j].Trim() + " ";
                }

                if (mm_MarkovTokens.ContainsKey(tokenArray[0].ToLower()))
                {
                    mm_MarkovTokens[tokenArray[0].Trim().ToLower()].Add(followingText.Trim());
                }
                else
                {
                    mm_MarkovTokens.Add(tokenArray[0].Trim().ToLower(), new List<String>());
                    mm_MarkovTokens[tokenArray[0].Trim().ToLower()].Add(followingText.Trim());
                }
            }

            //close the streams
            sr.Close();
            fs.Close();
        }

        
        /// <summary>
        /// outputs a file with generated text based on the current token association dictionary
        /// </summary>
        /// <param name="fileName">file to output</param>
        /// <param name="mm_MarkovTokens">token dictionary</param>
        public void output(String fileName, Dictionary<String, List<String>> mm_MarkovTokens)
        {
            //get the first key
            String token = mm_MarkovTokens.Keys.ToList<String>()[0];
            List<String> tokens;
            String output = token;
            int index = 0;

            //get 1000 token pairs
            for (int i = 0; i < 100; i++)
            {
                if (mm_MarkovTokens.ContainsKey(token.ToLower()))
                {
                    //find the first token
                    tokens = mm_MarkovTokens[token.ToLower()];
                }
                else
                {
                    break;
                }

                //select a random index within its associated list 
                //(this also keeps us within distribution parameters since i allow duplicate entries)
                index = mm_Rand.Next(0, tokens.Count - 1);

                //get the last word from the associated list, make it the next token
                token = tokens[index].Split(' ')[tokens[index].Split(' ').Length - 1];

                //append the chosen token list to the output string
                output += " " + tokens[index];
            }

            //create a file access stream
            FileStream fs = new FileStream(fileName, FileMode.Create);
            //create a stream writier to write the string
            StreamWriter sw = new StreamWriter(fs);

            //write the file
            sw.Write(output);

            //close the streams
            sw.Close();
            fs.Close();
        }
        
        /// <summary>
        /// outputs a file using the given token dictionaries
        /// </summary>
        /// <param name="filename">file to output</param>
        /// <param name="chainList">dictionaries to use</param>
        public void outputMultiTier(String filename, List<Dictionary<String, List<String>>> dictionaries, int numTokens)
        {
            //setup temporary variables
            int index = 0;
            List<String> tokens = new List<string>();
            
            //get the size of the keylist
            int size = dictionaries[0].Keys.ToList<String>().Count();
            
            //choose a key randomly
            String token = dictionaries[0].Keys.ToList<String>()[mm_Rand.Next(0, size - 1)];

            //variable to hold the output
            String output = "";

            //iterate through the desired number of tokens
            for (int i = 0; i <= numTokens; i++)
            {
                if (dictionaries[0].ContainsKey(token.ToLower()))
                {
                    //get the token list
                    tokens = dictionaries[0][token.ToLower()];
                }
                else
                {
                    break;
                }

                //select a random index within its associated list 
                //(this also keeps us within distribution parameters since i allow duplicate entries)
                index = mm_Rand.Next(0, tokens.Count - 1);

                //get the last word from the associated list, make it the next token
                token = tokens[index].Split(' ')[tokens[index].Split(' ').Length - 1];

                //pull the output from the tiers
                for (int j = 0; j < tokens.Count - 2; j++)
                {
                    output += " " + outputTier(dictionaries, tokens[j], tokens[j+1], 1);
                }
            }

            //create teh filestream and stream writer
            FileStream fs = new FileStream(filename, FileMode.Create);
            StreamWriter sw = new StreamWriter(fs);

            //write the file
            sw.Write(output);

            //close the streams
            sw.Close();
            fs.Close();

        }

        /// <summary>
        /// a recursive method to work its way down the tiered dictionaries and return a matching token run that begins
        /// with the toke and ends with the terminator
        /// </summary>
        /// <param name="dictionaries">a collection of usable token dictionaries in descending order</param>
        /// <param name="token">the token to match against</param>
        /// <param name="tier">the current tier in the dictionaries</param>
        /// <returns>the constructed token stream</returns>
        private String outputTier(List<Dictionary<String, List<String>>> dictionaries, String token, String terminator, int tier)
        {
            List<String> tokens = new List<String>();
            List<String> downSelect = new List<String>();
            String output = token;
            int index = 0;

            //check to see if we're the lowest tier
            if (tier == dictionaries.Count - 1)
            {
                //select the matching, add to output, return tokens
                foreach (String str in dictionaries[tier][token])
                {   //see if it matches the terminator
                    if (str.Split(' ')[str.Split(' ').Length - 1] == terminator)
                    {   //add to the downselect
                        downSelect.Add(str);
                    }
                }

                //if downselect was empty, build till you reach the terminator
                if (downSelect.Count == 0)
                {
                    //since there were no tokens downselected, generate tokens until you match
                    return findMatch(dictionaries[tier], token, terminator);
                }
                else
                {
                    //return one of the adequate matches
                    return downSelect[mm_Rand.Next(0, downSelect.Count - 1)];
                }
            }
            else //continue to break down the parse
            {
                //see if this dictionary has the seed token
                if (dictionaries[tier].ContainsKey(token))
                {
                    tokens = dictionaries[tier][token];
                }
                else
                {   //nothing you can do, return the output
                    return output;
                }

                //downselect token list
                foreach (String str in tokens)
                {
                    //check for end-token matches
                    if (str.Split(' ')[str.Split(' ').Length - 1] == terminator)
                    {
                        downSelect.Add(str);
                    }
                }

                //find the token
                if (downSelect.Count == 0)
                {
                    //since there were no tokens downselected, generate tokens until you match
                    tokens = findMatch(dictionaries[tier], token, terminator).Split(' ').ToList<String>();
                }
                else
                {
                    //randomly select one of the down selects
                    tokens = downSelect[mm_Rand.Next(0, downSelect.Count - 1)].Split(' ').ToList<String>();
                }
                //parse down the next tiers
                for (int i = 0; i < tokens.Count - 2; i++)
                {
                    output += " " + outputTier(dictionaries, tokens[i], tokens[i+1], tier + 1);
                }
            }

            return output;
        }

        /// <summary>
        /// builds tokens from the starting token until a matching token is found or a count limit of 100 is reached
        /// </summary>
        /// <param name="dictionary">the dictionary the run is built from</param>
        /// <param name="startToken">the starting token</param>
        /// <param name="matchToken">the ending token</param>
        /// <returns></returns>
        private String findMatch(Dictionary<String,List<String>> dictionary, String startToken, String matchToken)
        {
            String output = "";
            int countLimit = 0;
            List<String> tokens;
            String token = startToken;
            int index = 0;

            //loop while you still havent found a matching end token, nor exceeded the count limit
            while (!output.Contains(matchToken) || countLimit != 100 ) 
            {
                if (dictionary.ContainsKey(token.ToLower()))
                {
                    //find the first token
                    tokens = dictionary[token.ToLower()];
                }
                else
                {
                    countLimit++;
                    continue;
                }

                //select a random index within its associated list 
                //(this also keeps us within distribution parameters since i allow duplicate entries)
                index = mm_Rand.Next(0, tokens.Count - 1);

                //get the last word from the associated list, make it the next token
                token = tokens[index].Split(' ')[tokens[index].Split(' ').Length - 1];

                //append the chosen token list to the output string
                output += " " + tokens[index];

                countLimit++;
            }

            //were you able to find a completing string?
            if (!output.Contains(matchToken))
            {
                //couldnt not find string a string in time
                return "";
            }
            else
            {
                //return the completed string
                return output.Substring(0, output.IndexOf(matchToken));
            }
        }
    }

[update: removed some things that would cause tokens to merge and a potential infinite loop edge case]

the code that i used to call MarkovMusic:


 static void Main(string[] args)
        {
            MarkovMusic parser = new MarkovMusic();
            Dictionary<String, List<String>> markov5 = new Dictionary<String,List<String>>();
            Dictionary<String, List<String>> markov4 = new Dictionary<String, List<String>>();
            Dictionary<String, List<String>> markov3 = new Dictionary<String, List<String>>();
            Dictionary<String, List<String>> markov2 = new Dictionary<String, List<String>>(); 

            
            parser.parse("input.txt",5, markov5);
            parser.parse("input2.txt",5, markov5);
            parser.parse("input3.txt",5, markov5);
            parser.parse("input4.txt",5, markov5);
            parser.parse("input5.txt",5, markov5);

            parser.parse("input.txt", 4, markov4);
            parser.parse("input2.txt", 4, markov4);
            parser.parse("input3.txt", 4, markov4);
            parser.parse("input4.txt", 4, markov4);
            parser.parse("input5.txt", 4, markov4);

            parser.parse("input.txt", 3, markov3);
            parser.parse("input2.txt", 3, markov3);
            parser.parse("input3.txt", 3, markov3);
            parser.parse("input4.txt", 3, markov3);
            parser.parse("input5.txt", 3, markov3);

            parser.parse("input.txt", 2, markov2);
            parser.parse("input2.txt", 2, markov2);
            parser.parse("input3.txt", 2, markov2);
            parser.parse("input4.txt", 2, markov2);
            parser.parse("input5.txt", 2, markov2);
            

            
            List<Dictionary<String, List<String>>> chainList = new List<Dictionary<string, List<string>>>();

            chainList.Add(markov5);
            chainList.Add(markov4);
            chainList.Add(markov3);
            chainList.Add(markov2);

            parser.outputMultiTier("output.txt", chainList,10);

        }







#19 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 13 August 2011 - 06:39 AM


What if a run association isnt found? Then you build on your current token tier until you encounter a matching end token that completes the theme selection and then move on from there.

So that is the basis I'm thinking of, and it should produce a better result than my last video, but how much better remains to be seen. Hopefully I can get this coded this weekend, but we'll see.


I think you're at the point where the markov-chain isn't going to be enough on its own. I'm not a musician, but I would think you'll need to check the 'sanity' of the following:

1. Does the addition of sequence [t2 t3 t4 ... tN] stay in key?
2. Does the addition cause the sequence to lose tempo?
3. Does the sequence belong to the apporpriate section of the song? (most songs have middles, endings, etc..)

So, rather than just randomly pick from the chain, you need to pick, check, and if failed, pick a new one.

Something that would be really nice would be multi-voice compositions. So a melody voice and a bass voice for example. Basically each value in the markov chain would contain 2 values-- one for each voice. Could be tricky due to tempo/timing though...



By itself, you're right on those counts about the markov generator. I've taken it about as far as you can so that the markov 'intelligently' selects tokens based on desired token patterns.

Issue 1) it can be resolved in that as long as the samples are in the same key, the output will remain in the same key. However, accidentals (notes that are purposefully off-key) can still cause off-key notes and would need to be looked-out for. (in my latest ones i actually replaced an input sample because it had waaay too many accidentals in it such that the key should have never been defined as C (probably C minor).

Issue 2) if you allow the the generator to vary its tempo mid-song, yes this is an issue. you can also check tokens for notes that are of too-different timing (i.e., your song is based of of quarter notes, you dont want too many 16th notes showing up, but 8th notes may be ok). It would require adding more ABC related note information, but shouldn't be too hard.

Issue 3) thats could be done with some sort of weighting value as the parse moves forward through the song it gets interpolated between its distance from start and end between two threshold values. That would allow you to weight tokens for a section of the song and would be easy to add.

#20 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 14 August 2011 - 10:28 AM

found a bug in the above code that actually changes the way it builds the music... I'm working on the correction. I'll post it here when complete.

[update] turns out that the bug actually produced a better music flow, but didn't necessarily build the music as per the concept. Here is the revised code, however i still recommend the "buggy" previous one as it created a more improvised / alternate feel of the source files

public class MarkovMusic
    {
        /// <summary>
        /// basic random number generator
        /// </summary>
        private Random mm_Rand = new Random();

        /// <summary>
        /// basic constructor
        /// </summary>
        public MarkovMusic() { }

        /// <summary>
        /// parses a file into a markov token dictionary
        /// </summary>
        /// <param name="fileName">file to parse</param>
        /// <param name="n">token association size</param>
        /// <param name="mm_MarkovTokens">token dictionary</param>
        public void parse(String fileName, int n, Dictionary<String, List<String>> mm_MarkovTokens)
        {

            //get access to the input file
            FileStream fs = new FileStream(fileName, FileMode.Open);

            //setup the stream reader
            StreamReader sr = new StreamReader(fs);

            //holds tokens in work
            String[] tokenArray;

            //the text following the token
            String followingText = "";

            //create a word token placeholder for carryovers
            String tokenCarry = "";

            //keep reading the file until you reach the end
            while (!sr.EndOfStream)
            {
                //check to see if a token was carried over from last loop
                if (tokenCarry == "")
                {
                    //replace token carry with the line
                    tokenCarry = sr.ReadLine();
                }
                else
                {
                    //combine with tokenCarry 
                    tokenCarry += " " + sr.ReadLine();
                }

                //split tokenCarry into tokens
                tokenArray = tokenCarry.Trim().Split(' ');

                //create tokens and store them into the markov Token Dictionary
                for (int i = 0; i < tokenArray.Length; i++)
                {
                    //ensure you have room to define a new token entry
                    //[t1][t2 t3 - tn]<[EOL]
                    if ((i + n) < (tokenArray.Length - 1))
                    {
                        //calculate the following text
                        for (int j = 1; j <= n; j++)
                        {
                            followingText += tokenArray[i + j].Trim() + " ";
                        }

                        //check to see if the token already exists
                        if (mm_MarkovTokens.ContainsKey(tokenArray[i].ToLower()))
                        {
                            //it does exist, so add the new token association
                            mm_MarkovTokens[tokenArray[i].Trim().ToLower()].Add(followingText.Trim());
                        }
                        else
                        {
                            //it doesnt exist, so create it and its token association
                            mm_MarkovTokens.Add(tokenArray[i].Trim().ToLower(), new List<String>());
                            mm_MarkovTokens[tokenArray[i].Trim().ToLower()].Add(followingText.Trim());
                        }
                        //reset following text
                        followingText = "";
                    }
                    else
                    {
                        //calculate the following text
                        for (int j = i; j < tokenArray.Length; j++)
                        {
                            followingText += tokenArray[j].Trim() + " ";
                        }

                        //carry over the training text to the next iteration
                        tokenCarry = followingText.Trim();
                        followingText = "";
                        break;
                    }
                }
            }

            //it is likely we missed tokens near the end, do the same thing as we did above, but for them
            if (tokenCarry.Length > 0)
            {
                tokenArray = tokenCarry.Trim().Split(' ');

                //calculate the following text
                for (int j = 1; j < tokenArray.Length; j++)
                {
                    followingText += tokenArray[j].Trim() + " ";
                }

                if (mm_MarkovTokens.ContainsKey(tokenArray[0].ToLower()))
                {
                    mm_MarkovTokens[tokenArray[0].Trim().ToLower()].Add(followingText.Trim());
                }
                else
                {
                    mm_MarkovTokens.Add(tokenArray[0].Trim().ToLower(), new List<String>());
                    mm_MarkovTokens[tokenArray[0].Trim().ToLower()].Add(followingText.Trim());
                }
            }

            //close the streams
            sr.Close();
            fs.Close();
        }

        
        /// <summary>
        /// outputs a file with generated text based on the current token association dictionary
        /// </summary>
        /// <param name="fileName">file to output</param>
        /// <param name="mm_MarkovTokens">token dictionary</param>
        public void output(String fileName, Dictionary<String, List<String>> mm_MarkovTokens)
        {
            //get the first key
            String token = mm_MarkovTokens.Keys.ToList<String>()[0];
            List<String> tokens;
            String output = token;
            int index = 0;

            //get 1000 token pairs
            for (int i = 0; i < 100; i++)
            {
                if (mm_MarkovTokens.ContainsKey(token.ToLower()))
                {
                    //find the first token
                    tokens = mm_MarkovTokens[token.ToLower()];
                }
                else
                {
                    break;
                }

                //select a random index within its associated list 
                //(this also keeps us within distribution parameters since i allow duplicate entries)
                index = mm_Rand.Next(0, tokens.Count - 1);

                //get the last word from the associated list, make it the next token
                token = tokens[index].Split(' ')[tokens[index].Split(' ').Length - 1];

                //append the chosen token list to the output string
                output += " " + tokens[index];
            }

            //create a file access stream
            FileStream fs = new FileStream(fileName, FileMode.Create);
            //create a stream writier to write the string
            StreamWriter sw = new StreamWriter(fs);

            //write the file
            sw.Write(output);

            //close the streams
            sw.Close();
            fs.Close();
        }
        
        /// <summary>
        /// outputs a file using the given token dictionaries
        /// </summary>
        /// <param name="filename">file to output</param>
        /// <param name="chainList">dictionaries to use</param>
        public void outputMultiTier(String filename, List<Dictionary<String, List<String>>> dictionaries, int numTokens)
        {
            //setup temporary variables
            int index = 0;
            List<String> tokens = new List<string>();
            
            //get the size of the keylist
            int size = dictionaries[0].Keys.ToList<String>().Count();
            
            //choose a key randomly
            String token = dictionaries[0].Keys.ToList<String>()[mm_Rand.Next(0, size - 1)];

            //variable to hold the output
            String output = "";

            //iterate through the desired number of tokens
            for (int i = 0; i <= numTokens; i++)
            {
                if (dictionaries[0].ContainsKey(token.Trim()))
                {
                    //get the token list
                    tokens = dictionaries[0][token.Trim()];
                }
                else
                {
                    break;
                }

                //select a random index within its associated list 
                //(this also keeps us within distribution parameters since i allow duplicate entries)
                index = mm_Rand.Next(0, tokens.Count - 1);

                //pull the output from the tiers
                for (int j = 0; j < tokens[index].Split(' ').Length-2; j++)
                {
                    int l = tokens[index].Split(' ').Length;
                    output += " " + outputTier(dictionaries, tokens[index].Split(' ')[j], tokens[index].Split(' ')[j + 1], 1);
                }

                //get the last word from the associated list, make it the next token
                token = tokens[index].Split(' ')[tokens[index].Split(' ').Length - 1];
            }

            //create teh filestream and stream writer
            FileStream fs = new FileStream(filename, FileMode.Create);
            StreamWriter sw = new StreamWriter(fs);

            //write the file
            sw.WriteLine(output);

            //close the streams
            sw.Close();
            fs.Close();

        }

        /// <summary>
        /// a recursive method to work its way down the tiered dictionaries and return a matching token run that begins
        /// with the toke and ends with the terminator
        /// </summary>
        /// <param name="dictionaries">a collection of usable token dictionaries in descending order</param>
        /// <param name="token">the token to match against</param>
        /// <param name="tier">the current tier in the dictionaries</param>
        /// <returns>the constructed token stream</returns>
        private String outputTier(List<Dictionary<String, List<String>>> dictionaries, String token, String terminator, int tier)
        {
            List<String> tokens = new List<String>();
            List<String> downSelect = new List<String>();
            String output = token;
            int index = 0;

            //check to see if we're the lowest tier
            if (tier == dictionaries.Count - 1)
            {
                if (dictionaries[tier].ContainsKey(token.Trim()))
                {
                    //select the matching, add to output, return tokens
                    foreach (String str in dictionaries[tier][token.Trim()])
                    {   //see if it matches the terminator
                        if (str.Split(' ')[str.Split(' ').Length - 1] == terminator)
                        {   //add to the downselect
                            downSelect.Add(str);
                        }
                    }
                }
                else
                {
                    return output;
                }

                //if downselect was empty, build till you reach the terminator
                if (downSelect.Count == 0)
                {
                    //since there were no tokens downselected, generate tokens until you match
                    return findMatch(dictionaries[tier], token, terminator);
                }
                else
                {
                    //return one of the adequate matches
                    return downSelect[mm_Rand.Next(0, downSelect.Count - 1)];
                }
            }
            else //continue to break down the parse
            {
                //see if this dictionary has the seed token
                if (dictionaries[tier].ContainsKey(token.ToLower().Trim()))
                {
                    tokens = dictionaries[tier][token.ToLower().Trim()];
                }
                else
                {   //nothing you can do, return the output
                    return output;
                }

                //downselect token list
                foreach (String str in tokens)
                {
                    //check for end-token matches
                    if (str.Split(' ')[str.Split(' ').Length - 1] == terminator)
                    {
                        downSelect.Add(str);
                    }
                }

                //find the token
                if (downSelect.Count == 0)
                {
                    //since there were no tokens downselected, generate tokens until you match
                    tokens = findMatch(dictionaries[tier], token, terminator).Split(' ').ToList<String>();//(token + " " + terminator).Split(' ').ToList<String>();
                }
                else
                {
                    //randomly select one of the down selects
                    tokens = downSelect[mm_Rand.Next(0, downSelect.Count - 1)].Split(' ').ToList<String>();
                }
                //parse down the next tiers
                for (int i = 0; i < tokens.Count - 2; i++)
                {
                    //if (tokens[i] == "")
                    //{
                        //i++;
                    //}

                    output += " " +outputTier(dictionaries, tokens[i], tokens[i+1], tier + 1);
                }
            }

            return output;
        }

        /// <summary>
        /// builds tokens from the starting token until a matching token is found or a count limit of 100 is reached
        /// </summary>
        /// <param name="dictionary">the dictionary the run is built from</param>
        /// <param name="startToken">the starting token</param>
        /// <param name="matchToken">the ending token</param>
        /// <returns></returns>
        private String findMatch(Dictionary<String,List<String>> dictionary, String startToken, String matchToken)
        {
            String output = "";
            int countLimit = 0;
            List<String> tokens;
            String token = startToken;
            int index = 0;

            //loop while you still havent found a matching end token, nor exceeded the count limit
            while (!output.Contains(matchToken.Trim()) && countLimit < 100 ) 
            {
                if (dictionary.ContainsKey(token.ToLower().Trim()))
                {
                    //find the first token
                    tokens = dictionary[token.ToLower().Trim()];
                }
                else
                {
                    countLimit++;
                    continue;
                }

                //select a random index within its associated list 
                //(this also keeps us within distribution parameters since i allow duplicate entries)
                index = mm_Rand.Next(0, tokens.Count - 1);

                //get the last word from the associated list, make it the next token
                token = tokens[index].Split(' ')[tokens[index].Split(' ').Length - 1];

                //append the chosen token list to the output string
                output += " " + tokens[index];

                countLimit++;
            }

            //were you able to find a completing string?
            if (!output.Contains(matchToken))
            {
                //couldnt not find string a string in time
                return "";
            }
            else
            {
                //return the completed string
                int bracketOpen = 0;
                int bracketClosed = 0;
                for (int i = 0; i < output.Length; i++)
                {
                    if (output[i] == '[')
                        bracketOpen++;
                    if (output[i] == ']')
                        bracketClosed++;
                }

                //return output.Substring(0, output.IndexOf(matchToken.Trim())+1);
                if (bracketOpen == bracketClosed)
                    return output;
                else
                    return output + "]";
            }
        }
    }





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS