$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 descending size $validLengthGroup = array ( array(6,5,5), array(3,3,2), array(8,5,3), // a very poor and problematic representation of a triplet, but it does occur e.g. // with triplet (crotchet, crotchet rest, crotchet)=(8,3,5). However, in the quaver version of this // example the rest has disappeared altogether (3,NULL,1). so we have no chance! array(11,5), array(5,3) ); function is_valid_group ($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); if ($factor) foreach($lengthSet as $i=>$v) $lengthSet[$i] /= 3; } // then sort the array descending to match the convention in definition of validLengthGroup rsort($lengthSet); // now we are ready to check for a valid group of lengths foreach ($validLengthGroup as $thisgroup) { $result = false; // 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() == "Rest") return false; // this is caught anyway $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); } // Track the number of conversions $numConvertedTriplets = 0; // echo $clip->GetClipHeader()."\n"; // Use arrays $TripletQ and $lengthSet to hold candidates $TripletQ = array(); $lengthSet = array(); $tied_note_pending=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_grace = isset($o->Opts["Dur"]["Grace"]); $is_triplet = isset($o->Opts["Dur"]["Triplet"]); // check this $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 foreach ($tickLength as $notename => $value) { if (isset($opts["Dur"][$notename])) { $length = $value; } } 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; } 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 (is_valid_group($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; 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 (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"]); echo $this->ReconstructClipText()."\n"; $length_index++; } $TripletQ = array(); $lengthSet = array(); $tied_note_pending=false; break; // out of the while loop } elseif (sizeof($lengthSet)<3) { // not a triplet yet but there is still time so store data 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); $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 } 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; } // end if is_note else } // end for each clip if ($TripletQ) foreach($TripletQ as $this) { echo $this->ReconstructClipText()."\n"; } echo NWC2_ENDCLIP."\n"; if (!$numConvertedTriplets) { fputs(STDERR,"No valid triplets were found within the selection"); exit(NWC2RC_ERROR); } exit(NWC2RC_SUCCESS); ?>