$ticks_per_crotchet *4, "Half" => $ticks_per_crotchet *2, "4th" => $ticks_per_crotchet, "8th" => $ticks_per_crotchet /2, "16th" => $ticks_per_crotchet /4, "32nd" => $ticks_per_crotchet /8, "64th" => $ticks_per_crotchet /16 ); // NWC's efforts in terms of representing a triplet come from examples. There may be more to come. // When adding to this array, always insert sub-array elements in the order they have been observed. Also beware that // the further these ratios stray from (1,1,1),(2,1) or (1,2) the greater is the risk of false positive triplet identification $validLengthGroup = array ( array(6,5,5), array(5,6,5), array(3,3,2), array(8,3,5), // a very poor and potentially problematic representation of a triplet, but it does occur e.g. // with triplet (crotchet, crotchet rest, crotchet)=(8,3,5) if the import resolution is a bit low array(11,10,11), array(19,24,21), array(21,11), array(11,21), array(11,5), array(5,11), array(3,5) ); function isValidGroup ($lengthSet) { global $validLengthGroup; // first reduce the array by its HCF if (sizeof($lengthSet) <= 1) return false; $factor = true; while($factor) { foreach($lengthSet as $v) $factor &= !($v % 2); if ($factor) foreach($lengthSet as $i=>$v) $lengthSet[$i] /= 2; } $factor = true; while($factor) { foreach($lengthSet as $v) $factor &= !($v % 3); // just in case ticks_per_crotchet ever aquires a factor 3! if ($factor) foreach($lengthSet as $i=>$v) $lengthSet[$i] /= 3; } // now we are ready to check for a valid group of lengths foreach ($validLengthGroup as $thisgroup) { $result = false; // just in case the last group has different length to the array under test if (sizeof($lengthSet) != sizeof($thisgroup)) continue; $result = true; // assume a match until a mis-match is found foreach($lengthSet as $i=>$v) if ($lengthSet[$i] != $thisgroup[$i]) $result=false; if ($result) break; // found the winner, no need to go on searching } return $result; } function isTiedNote($arg) { if (($arg->GetObjType() != "Note") && ($arg->GetObjType() != "Chord") && ($arg->GetObjType() != "RestChord")) return false; $opts = $arg->GetOpts(); if (isset($opts["Pos"])) { $pos = $opts["Pos"]; if ($arg->GetObjType() == "Note") { $n = new NWC2NotePitchPos($pos); $ret = false; if ($n->Tied) $ret = true; unset($n); } else { // must be a Chord, so Pos is not a string but an array of strings $ret = false; foreach($opts["Pos"] as $k=>$v) { $n = new NWC2NotePitchPos($v); if ($n->Tied) $ret = true; // in practice, expect all or none to be tied unset($n); } } } else $ret=false; return ($ret); } function note_length ($arg) { // returns the base length of the item;s Dur - we deal with dotted etc elsewhere global $tickLength; $opts = $arg->GetOpts(); foreach ($tickLength as $notename => $value) if (isset($opts["Dur"][$notename])) $length = $value; return $length; } // Track the number of conversions $numConvertedTriplets = 0; // MAIN PROGRAM echo $clip->GetClipHeader()."\n"; // Use arrays $TripletQ and $lengthSet to hold candidates $TripletQ = array(); $lengthSet = array(); $tied_note_pending=false; $last_item_was_rest = false; foreach ($clip->Items as $item) { $o = new NWC2ClipItem($item); $opts = $o->GetOpts(); $is_note = in_array($o->GetObjType(), array("Chord","Note","Rest","RestChord")); // but there won't be a RestChord! $is_rest = ($o->GetObjType() == "Rest"); $is_grace = isset($o->Opts["Dur"]["Grace"]); $is_triplet = isset($o->Opts["Dur"]["Triplet"]); $is_tied = isTiedNote($o) ; $is_dotted = isset($o->Opts["Dur"]["Dotted"]); $is_dbldotted = isset($o->Opts["Dur"]["DblDotted"]); if ($is_note && !$is_grace && !$is_triplet) { if ($TripletQ) array_push($TripletQ,$o); else $TripletQ = array($o); // whatever happens, remember the note // evaluate its length $length = note_length($o); if ($is_dotted) $length *= 3/2; elseif ($is_dbldotted) $length *= 7/4; // there is at least one candidate in the queue, record length and check for triplet if ($tied_note_pending) { // add its length to that stored for the previous note $last_element = sizeof($lengthSet)-1; $lengthSet[$last_element] += $length; } elseif ($is_rest && $last_note_was_rest) { // similarly add length if (sizeof($lengthSet)==0) array_push($lengthSet, $length); // this really shouldn't happen! else { $last_element = sizeof($lengthSet)-1; $lengthSet[$last_element] += $length;} } else array_push($lengthSet, $length); if ($tied_note_pending = $is_tied) continue; // yes, really not "=="! this is ready for the next lap // check if we have a triplet while (true) { // start a loop so we can retest having discarded one note if (isValidGroup($lengthSet)) { //we have a triplet, output the notes in modified form $numConvertedTriplets++; $length = array_sum($lengthSet)/2; // this is the un-triplet-ised length of a single element // $dur = array_search($tickLength, $length); WHY DOESN'T THIS WORK? use alternative code foreach($tickLength as $key => $value) if ($value == $length) {$dur = $key; break; } // $durxtwo = array_search($tickLength, $length*2); WHY DOESN'T THIS WORK? foreach($tickLength as $key => $value) if ($value == $length *2) {$durxtwo = $key; break; } // Output the triplet, being two or three notes/rests/chords $output_tied_note_pending = false; $length_index = 0; $last_output_note_was_rest = false; foreach($TripletQ as $this) { if ($output_tied_note_pending) { $output_tied_note_pending = isTiedNote($this); continue; // dumping this note and processing the next item in the queue } if ($last_output_note_was_rest && ($this->GetObjType() == "Rest")) { // $last_output_note_was_rest is set anyway continue; // dumping this partial rest and processing the next item } if (isset($this->Opts["Opts"]["Beam"])) unset($this->Opts["Opts"]["Beam"]); if (isset($this->Opts["Opts"]["Stem"])) unset($this->Opts["Opts"]["Stem"]); // triplet stems don't need to be aligned // can't get rid of Opts altogether - a rest might have Opts["VertOffset"] set - not likely though if (isset($this->Opts["Dur"]["Dotted"])) unset($this->Opts["Dur"]["Dotted"]); // a dotted triplet makes no sense if (isset($this->Opts["Dur"]["DblDotted"])) unset($this->Opts["Dur"]["DblDotted"]); // nor does this! if (sizeof($lengthSet)==3) $this->Opts["Dur"] = array($dur => "","Triplet" => ""); elseif ($lengthSet[0] > $lengthSet[1]) { // first note is of double duration $this->Opts["Dur"] = array ((($length_index == 0) ? $durxtwo : $dur)=> "","Triplet" => ""); } else { // second note is of double duration $this->Opts["Dur"] = array ((($length_index == 0) ? $dur : $durxtwo)=> "","Triplet" => ""); } if ($length_index == 0) $this->Opts["Dur"]["Triplet"] = "First"; if ($length_index == (sizeof($lengthSet)-1)) $this->Opts["Dur"]["Triplet"] = "End"; $output_tied_note_pending = isTiedNote($this); // un-tie the note/chord if it is tied if (isset($this->Opts["Pos"]) && $output_tied_note_pending) { // i.e. not a rest if ($this->GetObjType() == "Note") { $pos = new NWC2NotePitchPos($this->Opts["Pos"]); $pos->Tied=false; $this->Opts["Pos"] = $pos->ReconstructClipText(); unset($pos); } else { // a Chord, must deal with all the Pos elements foreach($this->Opts["Pos"] as $k=>$v) { $pos = new NWC2NotePitchPos($v); $pos->Tied = false; $this->Opts["Pos"][$k] = $pos->ReconstructClipText(); unset($pos); } } } if ($this->GetObjType() == "Rest"){ unset($this->Opts["Pos"]); $last_output_note_was_rest = ($this->GetObjType() == "Rest"); } else $last_output_note_was_rest = false; echo $this->ReconstructClipText()."\n"; $length_index++; } $TripletQ = array(); $lengthSet = array(); $tied_note_pending=false; $last_output_note_was_rest=false; break; // out of the while loop } elseif ((sizeof($lengthSet)<3) || ($is_rest)) { // not a triplet yet but there is still time $last_note_was_rest = $is_rest; continue 2; // breaking out of the while loop to get a new item from the clip } else { // not a triplet so output the first note, drop it from the stored queue and retest $output_tied_note_pending = true; while ($output_tied_note_pending) { $this = array_shift($TripletQ); if (($this->GetObjType() == "Rest") && (note_length($this) < $lengthSet[0])) { // now it gets complicated because if the partial rest we have just removed from the queue was dotted // we should only strip off 2/3 of it and put the rest back in the queue! // NB ignore the double-dotted case - I have yet to see one of these! $this_opts = $this->GetOpts(); $this_is_dotted = isset($this->Opts["Dur"]["Dotted"]); if ($this_is_dotted) { // the rest to be output is of this duration but not dotted unset($this->Opts["Dur"]["Dotted"]); echo $this->ReconstructClipText()."\n"; $lengthSet[0] -= note_length($this); $this_length = note_length($this); // $this_dur = array_search($tickLength, $length); WHY DOESN'T THIS WORK? use alternative code foreach($tickLength as $key => $value) if ($value == $this_length) {$this_dur = $key; break; } unset($this->Opts["Dur"][$this_dur]); $this_length /= 2; // $this_dur = array_search($tickLength, $length); WHY DOESN'T THIS WORK? use alternative code foreach($tickLength as $key => $value) if ($value == $this_length) {$this_dur = $key; break; } $this->Opts["Dur"][$this_dur]=""; $new_size = array_unshift($TripletQ,$this); continue 2; // break out of this while loop and retest for a triplet } else { // not dotted so it's much simpler echo $this->ReconstructClipText()."\n"; $lengthSet[0] -= note_length($this); continue 2; // break out of this while loop and retest for a triplet } } else { // a note, chord or a single rest $output_tied_note_pending = isTiedNote($this); echo $this->ReconstructClipText()."\n"; } } $this = array_shift($lengthSet); // dump its length too // Now we must retest because the remaining notes, if any, could be a (2 note) triplet if ($TripletQ) continue; // repeat the while loop } // end if triplet, maybe triplet or not triplet } // end while(true)loop $last_note_was_rest = $is_rest; } else { // not a note, sequence is spoiled so output everything in the queue, plus this non-note item and start afresh if ($TripletQ) foreach($TripletQ as $this) { echo $this->ReconstructClipText()."\n"; } echo $o->ReconstructClipText()."\n"; $TripletQ = array(); $lengthSet = array(); $tied_note_pending=false; $last_note_was_rest = false; } // end if is_note else unset($o); //??? } // end for each clip if ($TripletQ) foreach($TripletQ as $this) { echo $this->ReconstructClipText()."\n"; } echo NWC2_ENDCLIP."\n"; if (!$numConvertedTriplets) { fputs(STDERR,"No triplets were found within the selection. Check the MIDI import resolution used."); exit(NWC2RC_ERROR); } exit(NWC2RC_SUCCESS); ?>