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.
[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: 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 [/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.
Well done!! Fantastic! Documented from start to finish-- you deserve some sort of GameDev poster of the month award.
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.
Well done!! Fantastic! Documented from start to finish-- you deserve some sort of GameDev poster of the month award.
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.
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.
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...
/// <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.ToLower())) { //it does exist, so add the new token association mm_MarkovTokens[tokenArray.Trim().ToLower()].Add(followingText.Trim()); } else { //it doesnt exist, so create it and its token association mm_MarkovTokens.Add(tokenArray.Trim().ToLower(), new List<String>()); mm_MarkovTokens[tokenArray.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();
//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, 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>>();
[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.
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.
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> /// 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.ToLower())) { //it does exist, so add the new token association mm_MarkovTokens[tokenArray.Trim().ToLower()].Add(followingText.Trim()); } else { //it doesnt exist, so create it and its token association mm_MarkovTokens.Add(tokenArray.Trim().ToLower(), new List<String>()); mm_MarkovTokens[tokenArray.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();
//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++; //}
/// <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 == '[') bracketOpen++; if (output == ']') bracketClosed++; }