• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
NetGnome

Procedural Music Generation

23 posts in this topic

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:

[url="http://www.youtube.com/watch?v=lOjV5eDXkyc"]http://www.youtube.c...h?v=lOjV5eDXkyc[/url]

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:

[list=1][*]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)[/list]
0

Share this post


Link to post
Share on other sites
[quote name='Net Gnome' timestamp='1312408472' post='4844226']
So i guess to sum it up:

[list=1][*]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)[/list][/quote]

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.).
1

Share this post


Link to post
Share on other sites
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.
0

Share this post


Link to post
Share on other sites
[quote name='Net Gnome' timestamp='1312413379' post='4844253']
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.
[/quote]

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 :)
0

Share this post


Link to post
Share on other sites
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:

"[i]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.[/i]"

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.

[code]

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();
}


}

[/code]

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
0

Share this post


Link to post
Share on other sites
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: [url="http://en.wikipedia.org/wiki/ABC_notation"]http://en.wikipedia....ki/ABC_notation[/url] 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
0

Share this post


Link to post
Share on other sites
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:

[code]

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]

[/code]

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:

[code]

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]

[/code]

Here is the updated code:




[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();
}


}

[/code]

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




2

Share this post


Link to post
Share on other sites
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
0

Share this post


Link to post
Share on other sites
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?
0

Share this post


Link to post
Share on other sites
Best thing I can recommend (there is rather a derth of good ABC players) is to goto: [url="http://sites.google.com/site/lotroabc/"]http://sites.google.com/site/lotroabc/[/url] 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.

[code]

X:1
T:Chrono Cross - Timescar (Special gamedev.net truncated version :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

[/code]

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

Share this post


Link to post
Share on other sites
[quote name='Net Gnome' timestamp='1312933067' post='4846956']
Best thing I can recommend (there is rather a derth of good ABC players) is to goto: [url="http://sites.google.com/site/lotroabc/"]http://sites.google.com/site/lotroabc/[/url] 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.

[/quote]


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
0

Share this post


Link to post
Share on other sites
[quote name='willh' timestamp='1312938453' post='4846978']
[quote name='Net Gnome' timestamp='1312933067' post='4846956']
Best thing I can recommend (there is rather a derth of good ABC players) is to goto: [url="http://sites.google.com/site/lotroabc/"]http://sites.google.com/site/lotroabc/[/url] 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.

[/quote]


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
[/quote]

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

Share this post


Link to post
Share on other sites
[quote name='Net Gnome' timestamp='1312966414' post='4847079']
Here is the first 30s of the generated song: [url="http://www.youtube.com/watch?v=q8m73PKdYik"]http://www.youtube.c...h?v=q8m73PKdYik[/url]
[/quote]


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

Share this post


Link to post
Share on other sites
[quote name='willh' timestamp='1313007830' post='4847303']
[quote name='Net Gnome' timestamp='1312966414' post='4847079']
Here is the first 30s of the generated song: [url="http://www.youtube.com/watch?v=q8m73PKdYik"]http://www.youtube.c...h?v=q8m73PKdYik[/url]
[/quote]


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.
[/quote]

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.


http://www.youtube.com/watch?v=B0obrR-CSyA

0

Share this post


Link to post
Share on other sites
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.



0

Share this post


Link to post
Share on other sites
[quote name='Net Gnome' timestamp='1313171642' post='4848339']

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.
[/quote]

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

Share this post


Link to post
Share on other sites
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:
[url="http://www.youtube.com/watch?v=YJe05wQglDw"]http://www.youtube.c...h?v=YJe05wQglDw[/url]

A fast tune:
[url="http://www.youtube.com/watch?v=mohqgYDZL_g"]http://www.youtube.c...h?v=mohqgYDZL_g[/url]

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 =/ )

[code]
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]
[/code]



Here is the updated code:

[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));
}
}
}[/code]

[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:

[code]

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);

}

[/code]




1

Share this post


Link to post
Share on other sites
[quote name='willh' timestamp='1313192138' post='4848458']
[quote name='Net Gnome' timestamp='1313171642' post='4848339']
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.
[/quote]

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...
[/quote]


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

Share this post


Link to post
Share on other sites
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

[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.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 + "]";
}
}
}[/code]
0

Share this post


Link to post
Share on other sites
I think people that are interested in procedural music generation might be interested in a program called Band-in-a-Box, particularly if you want to generate popular music (jazz, blues, rock & roll, pop...). It's a complicated GUI with too many bells and whistles for my taste (and quite a few bugs), but it's easy to use to generate a very convincing rhythm section for a tune, given the chord changes. There are many different packages including different sets of instruments and styles. The so-called RealTracks are particularly good, and I think are worth mentioning in this thread because they generate music in a much different form than what has been described so far. They basically recorded some musicians for hours playing over common chord changes, divided the performance into phrases and the software will put together a whole track by pasting together these phrases, trying to match the chords in the recording with those in your tune. The results are not easy to distinguish from a real band performance. You can also add soloist tracks, but I find them less convincing.

I probably should have mentioned this approach earlier, but I somehow forgot about this piece of software when I wrote my first post in this thread.
2

Share this post


Link to post
Share on other sites
[quote name='way2lazy2care' timestamp='1313419493' post='4849395']
Can it support multiple instruments?
[/quote]

i supose it could in a way. If you parsed through the melody, then used the same random seed for the harmony and parsed it separately. then gave them both the same random seed when producing the output, it 'may' align. I have not tested this though. While the first bar or two may align,i would expect them to diverge eventually, though controlling the random seed may help.
0

Share this post


Link to post
Share on other sites
Hi,
I don't mean to zombie and old thread but I wanted to share my experiments with procedural song generation.
My technique is different from a HMM. I use a dynamic hierarchy method and allow each instrument to apply a small influence to the others in terms of energy.
You can listen to samples [url="http://www.kerneltrick.com/?p=21"]Here[/url]
Everything is dynamically generated at once. If anyone is curious I give more details about how the program is structured.
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0