// Generate a random score which can be mutated while playing. // There are two kinds of basic score nodes: instrument and rest; // and three kinds of combinations: concatenation, louder, and // transpose-lower-by-a-fifth. // Actually this generates a directed acyclic graph with, potentially, // many roots, so you can get many probably-related scores out of it. // You can initiate an iteration over any one of the scores. // FUCK. #include enum { max_score_size = 256 }; enum node_type { instrument_type, rest_type, concat_type, louder_type, transpose_type, n_node_types }; typedef unsigned char node_id, node_type; static node_type types[max_score_size]; static node_id lefts[max_score_size], rights[max_score_size]; static void generate_score() { int ii; for (ii = 0; ii < max_score_size; ii++) { types[ii] = rand() % n_node_types; lefts[ii] = rand() % ii; rights[ii] = rand() % ii; } } /* Okay, so how does iteration work? We need to maintain some kind of a stack. The stack needs a level per intermediate node, and when we generate a note, we have to pass it back up the stack to apply the various effects. Louder and transpose don't need any special "iteration state", but concatenation does (it needs to know whether it's descended to its left or right child --- although I guess you could maybe look at the rest of the stack to see that? Unless its two children are the same.), and to get concatenation's special time stretching feature to work, where the first and second halves take the same time, you need to be able to figure out how much time that should be --- but that's not a property of a single iteration, but of the concatenation node. One thing I'm not totally clear about is how you handle time. If there are 7 beats with no note before the next note, should you have to ask for the next note 8 times before the iterator gives it to you? Or should it give it to you right away, with a caution that it's embargoed for 7 more beats? Time comes from concatenation nodes; the first answer sort of implies that each concatenation node iteration state maintains a counter of how many timesteps remain in its frame of reference before the next item comes up, while the second one just implies that we add a delay to the note on its way up through the concatenation nodes on the stack when it's coming from the second child. */ static char log_durations[max_score_size]; static char compute_log_duration(node_id ii) { switch (node_type[ii]) { case instrument_type: case rest_type: return 0; case concat_type: { char a_len = log_durations[lefts[ii]], b_len = log_durations[rights[ii]]; return 1 + ((a_len < b_len) ? b_len : a_len); } default: return log_durations[lefts[ii]]; } } static void compute_durations() { var ii; for (ii = 0; ii < max_score_size; ii++) { log_durations[ii] = compute_log_duration(ii); } }