Skip to main content

Topic: Imported triplets (Read 19081 times) previous topic - next topic

Imported triplets
When NWC imports a MIDI file it does not faithfully represent triplets; correcting them manually is a tedious business so I have written a User Tool to do this.

It depends on correctly recognising the approximations that NWC generates during the import process. My current understanding of these is based on examples that I have exported and re-imported. I invite those who know NWC from the inside to help me to do better.

-- Brian

Code: [Select]
<?php
/*******************************************************************************
brm_Triplify Version 1.00

This script will seek sequences that NWC has created from importing a MIDI file containing
triplets and convert those sequences into triplets.

History:
[2009-02-12] Version 1.00 - Initial version
*******************************************************************************/
require_once("lib/nwc2clips.inc");

$clip = new NWC2Clip('php://stdin');

// we shall need to assess the lengths of several notes etc, including tied notes, in units of ticks
$ticks_per_crotchet = 16; // as small as possible, since we will not be representing triplets
$tickLength = array(
"Whole"=> $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);

?>

Re: Imported triplets
Reply #1
G'day Brian,
just did a quick test with a file I originally created to test Bryan Creer's Swing tool.  Took out the Swing staff, exported to MIDI and re-imported.  Unfortunately your tool didn't recognise any triplets.

Please find attached the files in question...  You'll need to rename the extension of the MIDI file - can't post MIDI's here...
LPTripTest.nwc = original file
LPTripTest-mid.txt = MIDI output file (needs rename)
LPTripTest-postMIDIimport.nwc = imported MIDI

MIDI import parameters:
All checked; 16th note resolution and quarter rest resolution
I plays 'Bones, crumpets, coronets, floosgals 'n youfonymums - gonna lern tubies next

Re: Imported triplets
Reply #2
Lawrie,

The answer lies in the MIDI import parameters you have used: "16th note resolution and quarter rest resolution". At this resolution your example triplets are very poorly represented, the note length ratios being (1,2,1). I could add this to my array of valid 'triplet signatures' but I fear that would lead to too many false positives (though when I tried this, there are none coming from your test file).

I generally use 32nd note resolution, which I assume is the default, and then your triplets come out as (3,3,2). With this change, the triplets in bars 5 and 7 are correctly recognised but in bars 13 and 15 a triplet is recognised one note too early; this is due to my accepting the triplet ratios in any order - so maybe I shouldn't do that.

This can easily be changed by commenting out the sort at line 57, though it is then necessary to modify the comment at line 28 and the subsequent triplet signature array so that note length ratios are always presented in the order they have been observed to occur, rather than descending size:
Code: [Select]
// When adding to this array, always insert sub-array elements in the order they have been observed
$validLengthGroup = array (
array(6,5,5),
array(3,3,2),
array(8,3,5), // 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(1,2,1), // another poor one, included to cope with Lawrie's test file
array(11,5),
array(3,5)
);
Here I have also added a triplet signature (1,2,1) which is needed to cope with your re-import of your test file but I do think this is a bad idea for the reasons stated!

As I said in the original post, the tool is programmed to recognise the sequence of notes that NWC generates as an approximation to a triplet, based on the ratio of their lengths, but of course I have only put in the 'triplet signatures' that I have seen as a result of exporting and re-importing various test triplets. How much better it would be if I had the inside information that would enable me to predict what NWC will do in any import situation!

In getting to the bottom of why your test file was not ideally handled, I now understand my own comment in the code snippet above, so I need to change that too. With a rest resolution of a quarter, NWC doesn't have any chance to represent a quaver triplet rest!

Finally, NWC itself is in the best position to recognise triplets when importing MIDI files because at that stage the full resolution is available. Why doesn't it attempt this?

-- Brian

PS I had intended to attach a NWC file to this response as you did in your post; how does one do that?

Re: Imported triplets
Reply #3
<snip>
Finally, NWC itself is in the best position to recognise triplets when importing MIDI files because at that stage the full resolution is available. Why doesn't it attempt this?
Yep, after I'd posted I thought about my import parameters and figured that might be the case - thanks for confirming it.  I agree that a mod to cope with my poor import is probably not ideal.

Quote
PS I had intended to attach a NWC file to this response as you did in your post; how does one do that?

You need to upgrade yourself to NWC2User and then there is an "Additional Options..." link that opens up and allows attachments plus some additional options.

Go here:
https://forum.noteworthycomposer.com/?topic=5483.0
and follow the prompts.
I plays 'Bones, crumpets, coronets, floosgals 'n youfonymums - gonna lern tubies next

Re: Imported triplets
Reply #4
Lawrie,

Thanks for your input. This has raised issues about the range of MIDI import resolutions that should be accommodated, also the need to deal with concatenated rests. I can see a way to deal with these and will post a new version when I have had the time to test it out thoroughly.

-- Brian

Re: Imported triplets
Reply #5
G'day Brian,
out of curiosity, have you looked at Andrew Purdam's "tripletise" user tool?

Scripto: http://nwc-scriptorium.org/nwc2scripts_trip.html

I plays 'Bones, crumpets, coronets, floosgals 'n youfonymums - gonna lern tubies next

Re: Imported triplets
Reply #6
Oh dear! Have I re-invented the wheel? I will have a look before I go much further. If nothing else, I have learnt a lot about the API.

-- Brian

Re: Imported triplets
Reply #7
I have now revised my User Tool to deal with approximations to triplets imported by NWC from a MIDI file; the new version appears below (brm_triplify).

The main change is to recognise that not everybody will import their MIDI file using the default parameters, so the tool certainly has to cope with finer resolutions. Coarser resolutions are problematic because as the approximation to a perfect triplet gets worse, the risk of falsely identifying a sequence of notes as a triplet becomes greater and there comes a point where it is best to skip these.

The other change is to handle concatenated rests. The need for this really only becomes apparent at high resolution and it is tricky: adjacent rests behave like tied notes, except that there is no indication which ones are notionally tied together. It gets worse: NWC will happily generate, for example, a dotted 4th rest, where one component - the 4th rest - does not form part of a triplet but the dotted bit - an 8th rest - does. I hope it doesn't churn out double-dotted rests - I could cater for them but I haven't.

I attach my test file which contains, on the top stave, a variety of triplets and, on the other staves, the result of exporting and re-importing at different resolutions. No parameters are required when invoking the tool; it will work with what it is given and convert to triplets any sequence that can safely be identified.

Lawrie drew my attention to Andrew Purdam's "tripletise" user tool but this requires very specific steering; for example the first triplet in my test file, at 64th note resolution (on the bottom stave) requires steering parameters "4 8t d32 16t 64t 16t 64 8t d32". It took longer to work that out than to do the edit manually, especially since it dealt with only one other triplet in the clip. I wanted something that will automatically unscramble as much as possible of the mess that NWC makes of importing triplets, with as little intervention as possible by the user.

I have no doubt that there are other triplet 'signatures' that I should have included but haven't; that is what the Newsgroup is for!

Finally, since it hasn't evoked a reponse yet, I repeat the question: why doesn't NWC make a better job of importing triplets in the first place, when exact information is available?

-- Brian

Code: [Select]
<?php
/*******************************************************************************
brm_Triplify Version 1.01

Seeks the triplet approximation sequences that NWC has created when importing a MIDI file
and converts those sequences into normal triplets.

History:
[2009-02-14] Version 1.01 - Recognition of triplets from a wider range of MIDI import
     resolutions and handling of concatenated rests
[2009-02-12] Version 1.00 - Initial version
*******************************************************************************/
require_once("lib/nwc2clips.inc");

$clip = new NWC2Clip('php://stdin');

// we shall need to assess the lengths of several notes etc, including tied notes, in units of ticks
$ticks_per_crotchet = 16; // as small as possible, since we will not be representing triplets
$tickLength = array(
"Whole"=> $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);

?>

Re: Imported triplets
Reply #8
I hope it doesn't churn out double-dotted rests
It doesn't. It will churn out chords in places where it sees a fast run of single notes.
Registered user since 1996

Re: Imported triplets
Reply #9
Quote
Finally, since it hasn't evoked a reponse yet, I repeat the question: why doesn't NWC make a better job of importing triplets in the first place, when exact information is available?
that would be 'cos we don't really know the answer.  There is some surmise that it may be to restrict exposure to copyright violation...  for myself, I don't know and in the time I've been on this forum Eric hasn't revealed his reasons for not improving those areas of MIDI import.

I haven't downloaded the new version yet - no time to test for now.
I plays 'Bones, crumpets, coronets, floosgals 'n youfonymums - gonna lern tubies next

Re: Imported triplets
Reply #10
It will churn out chords in places where it sees a fast run of single notes.
Rick,

Do you have an example of this? I have not managed to generate one.

-- Brian

Re: Imported triplets
Reply #11
Rename the attachment to: tt.mid
The left hand notes are arpeggiated, but NWC imports them as Chords.

I'm not saying NWC is wrong here, just that this is yet another thing that anyone hoping to decypher MIDI import needs to know.
Registered user since 1996

Re: Imported triplets
Reply #12
Rick, I don't understand how this example illustrates the behaviour you mentioned, viz rapid single notes imported as a chord; indeed it shows chords imported as single notes.

The left hand notes are arpeggiated, but NWC imports them as Chords. I'm not saying NWC is wrong here,
The left hand notes start almost at the same time and are shown as a chord but thereafter, as notes are added to the arpeggio, instead of adding notes to the chord, NWC cuts off the previous note/chord before starting the next. Maybe it would be too complicated to do anything else; it seems a reasonable import strategy.

Quote
just that this is yet another thing that anyone hoping to decypher MIDI import needs to know.
It is worth saying that any automated tool (even NWC itself) would struggle to deal with a source like your example, having a degree of randomness; presumably this one is was played in real time. It has, at least, shown me a small bug in the code at line 47, which should be
Code: [Select]
$tied_note_pending=false; $last_note_was_rest = false;

in the time I've been on this forum Eric hasn't revealed his reasons for not improving those areas of MIDI import.
I can only hope that Eric will be moved to share his thoughts on the subject. ;-)

-- Brian

Re: Imported triplets
Reply #13
Rick, I don't understand how this example illustrates the behaviour you mentioned, viz rapid single notes imported as a chord; indeed it shows chords imported as single notes.
Much depends on the definition of a chord.
Registered user since 1996

Re: Imported triplets
Reply #14
Quote
It has, at least, shown me a small bug in the code at line 47, which should be

Line 47?

Of course you mean line 117.
The variable $last_item_was_rest is never used anywhere else.

Re: Imported triplets
Reply #15
Of course you mean line 117.
The variable $last_item_was_rest is never used anywhere else.
Yes, indeed I do mean line 117; thanks for pointing it out. Changing the initialisation was the line of least resistance even though $last_item_was_rest is the more appropriate name.

-- Brian

Re: Imported triplets
Reply #16
After the latest modifications to the user tool framework, brm_Triplify doesn't work anymore.

It gets:
Quote
Fatal error: Cannot re-assign $this in C:\Programmi\NoteWorthy Composer 2\Scripts\brm_Triplify.php on line 162

The problem is the name "$this" clashing with the new libraries.

To solve the problem simply rename all the instances of "$this" in brm_Triplify as, for example, $this_triplet.

Re: Imported triplets
Reply #17
Quote
The problem is the name "$this" clashing with the new libraries.

Brian may be using an old version of PHP - I think NWC used to use version 3 or so.  He may not see this problem with "$this" until he upgrades to the version 5 PHP that the latest NWC now uses.

Brian, if you upgrade to PHP 5 [and I think it's reasonable to expect all users of your script to upgrade], within function isValidGroup, I think you could just use in_array in place of the last foreach loop, since PHP supports arrays as the "needle" as of version 4.2:
     return in_array($lengthSet, $validLengthGroup);

You could perhaps similarly avoid a foreach loop in function note_length, using array_intersect_key:
     return array_shift(array_intersect_key($tickLength, $opts["Dur"]));

Other miscellaneous comments:
- The "if ($TripletQ) array_push..." could probably be simply "$TripletQ[] = $o;"
- Your 2 array_search's didn't work because you have the 2 parameters reversed: some functions are needle/haystack, and others are haystack/needle :-(
- Your first "continue 2" could probably simply be a "break"? - your last "continue" could be removed?

Re: Imported triplets
Reply #18
The NWC2 User Tool Starter Kit started with PHP4. The latest kit includes PHP5, which has a new object model which enforces a more restrictive use of a $this variable (which is predefined as an instance pointer in class object methods).