Skip to main content

Show Posts

This section allows you to view all Show Posts made by this member. Note that you can only see Show Posts made in areas you currently have access to.

Messages - Brian Maskell

1
General Discussion / Re: Upgrade problems?
My query relates to the name of the selector rather than its contents. For example "Selector 01" has the tooltip "Clef Selector (c)" which is displayed when hovering over it; however it appears in View->Toolbars->Customize->Toolbar:Insert as "Selector 01". Can it be renamed as "Clef Selector"?
2
General Discussion / Re: Upgrade problems?
Is there a way to assign more helpful labels to ten toolbar item selectors that are used? What can be done with the six that appear not to be used at present?
Selector 01 Clef Selector (c)
Selector 02 Key Signature Selector (k)
Selector 03 Time Signature Selector (g)
Selector 04 Note Chord Selector (h)
Selector 05 Rest Selector
Selector 06 Bar Selector (b)
Selector 07 Dynamics Selector (d)
Selector 08 Tempo Variance Marks (e)
Selector 09 Pedal Marks (u)
Selector 10 Note Space
Selector 11
Selector 12
Selector 13
Selector 14
Selector 15
Selector 16
4
General Discussion / Skipping a section of music
My choir is rehearsing Borodin's Polovtsian Dances and I am using Richard Woodroffe's version (http://nwc-scriptorium.org/db/classical/b/boropold.nwc). However we are not singing all 629 bars! (That's a lot of notes Richard.)

I don't want actually to remove the cut sections, just skip over them, so I added on each stave:
at bar 233 a special ending (D) and the text 'cut to 369'
at bar 369 a special ending (1)
at bar 433 a special ending (D) and the text 'cut to 571'
at bar 571 a special ending (1)

The second cut works fine but in the first, play resumes at bar 321. If I add:
at bar 321 a special ending (D)
that cures the problem; play now resumes as required at 369 but what was going wrong at bar 321 initially and is there a better way to do this?

The working version is attached.
5
General Discussion / Re: Changing accidentals
You might find useful the two user tools, to favour sharps or flats, that I have just published in the User Tools forum. I have found them useful and your query has spurred me to make them generally available.
6
User Tools / Tools to favour sharps or flats in chords
These two user tools make it easier to adjust the spelling of accidentals, where the NWC default is not what is required. It is usually safest to Force Accidentals before using these tools.

Copy the code and save it in your Scripts folder as: brm_FavourSharps.php
Code: [Select · Download]
<?php
/*******************************************************************************
brm_FavourSharps 1.00

Brian Maskell

History:
[2010-09-29] Version 1.00 - Initial version
*******************************************************************************/
require_once("lib/nwc2clips.inc");

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

// MAIN PROGRAM

echo $clip->GetClipHeader()."\n";

foreach ($clip->Items as $item) {
    $o = new NWC2ClipItem($item);

    if (($o->GetObjType()=="Note")||($o->GetObjType()=="Chord")) {
$opts = $o->GetOpts();
$pos = $opts["Pos"];
if (is_array($pos)) {
   for ($i=0; $i<count($pos); $i++) {
        $n = new NWC2NotePitchPos($pos[$i]);
if ($n->Accidental=="b") {
   $n->Accidental="#";
      $n->Position--;
      $pos[$i]=$n->ReconstructClipText();
      unset($n);
}
   }
} else {
        $n = new NWC2NotePitchPos($pos);
if ($n->Accidental=="b") {
   $n->Accidental="#";
      $n->Position--;
      $pos=$n->ReconstructClipText();
      unset($n);
}
}
$o->Opts["Pos"] = $pos;
    } elseif ($o->GetObjType()=="RestChord") {
$opts = $o->GetOpts();
$pos = $opts["Pos2"];
if (is_array($pos)) {
   for ($i=0; $i<count($pos); $i++) {
        $n = new NWC2NotePitchPos($pos[$i]);
if ($n->Accidental=="b") {
   $n->Accidental="#";
      $n->Position--;
      $pos[$i]=$n->ReconstructClipText();
      unset($n);
}
   }
} else {
        $n = new NWC2NotePitchPos($pos);
if ($n->Accidental=="b") {
   $n->Accidental="#";
      $n->Position--;
      $pos=$n->ReconstructClipText();
      unset($n);
}
}
$o->Opts["Pos2"] = $pos;
    } else {
       // just echo the item unchanged
    }
    echo $o->ReconstructClipText()."\n";
}
echo NWC2_ENDCLIP."\n";

exit(NWC2RC_SUCCESS);
// exit(NWC2RC_REPORT); // alternative for debugging

?>

Copy the code and save it in your Scripts folder as: brm_FavourFlats.php
Code: [Select · Download]
<?php
/*******************************************************************************
brm_FavourFlats 1.00

Brian Maskell

History:
[2010-01-22] Version 1.00 - Initial version
*******************************************************************************/
require_once("lib/nwc2clips.inc");

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

// MAIN PROGRAM

echo $clip->GetClipHeader()."\n";

foreach ($clip->Items as $item) {
    $o = new NWC2ClipItem($item);

    if (($o->GetObjType()=="Note")||($o->GetObjType()=="Chord")) {
$opts = $o->GetOpts();
$pos = $opts["Pos"];
if (is_array($pos)) {
   for ($i=0; $i<count($pos); $i++) {
        $n = new NWC2NotePitchPos($pos[$i]);
if ($n->Accidental=="#") {
   $n->Accidental="b";
      $n->Position++;
      $pos[$i]=$n->ReconstructClipText();
      unset($n);
}
   }
} else {
        $n = new NWC2NotePitchPos($pos);
if ($n->Accidental=="#") {
   $n->Accidental="b";
      $n->Position++;
      $pos=$n->ReconstructClipText();
      unset($n);
}
}
$o->Opts["Pos"] = $pos;
    } elseif ($o->GetObjType()=="RestChord") {
$opts = $o->GetOpts();
$pos = $opts["Pos2"];
if (is_array($pos)) {
   for ($i=0; $i<count($pos); $i++) {
        $n = new NWC2NotePitchPos($pos[$i]);
if ($n->Accidental=="#") {
   $n->Accidental="b";
      $n->Position++;
      $pos[$i]=$n->ReconstructClipText();
      unset($n);
}
   }
} else {
        $n = new NWC2NotePitchPos($pos);
if ($n->Accidental=="#") {
   $n->Accidental="b";
      $n->Position++;
      $pos=$n->ReconstructClipText();
      unset($n);
}
}
$o->Opts["Pos2"] = $pos;
    } else {
       // just echo the item unchanged
    }
    echo $o->ReconstructClipText()."\n";
}
echo NWC2_ENDCLIP."\n";

exit(NWC2RC_SUCCESS);
// exit(NWC2RC_REPORT); // alternative for debugging

?>

7
General Discussion / Re: Grace notes exported to NWC 1.75
You are right, Rich: the viewer has improved a lot since I last had a serious look at it. I especially like the ability to slow down to a percentage of the original tempi. I shall try recommending it and see what happens. Thanks.
8
General Discussion / Re: Grace notes exported to NWC 1.75
Thanks for the analysis and for the suggestion; some already do use the viewer but I am reluctant to push people in that direction because it is a much less flexible, and hence less useful, tool than either NWC1 or NWC2. For example, when note-bashing a difficult passage I sometimes need to hear (just) that passage several times; starting the viewer anywhere but at the beginning is rather approximate.  For this reason I adopted v1.75 files as the lowest common denominator for publication.

I think my work-around must be to use only the configurations illustrated in bars 1 and 4 of my example. They don't need any attributes to export reasonably and they display well (though not identically) using any of the programs; the addition of a Lyric:Never attribute by the export process is unnecessary but does no harm.

I too have never found any issues in having NWC1, NWC1 Player, NWC2 and NWC2 Viewer all installed.

9
General Discussion / Grace notes exported to NWC 1.75
I use NWC (v2.5.5) to prepare music as a note-bashing aid to my choir. Because some choir members still use NWC1, I export to v1.75 before publishing. So I am concerned with the way the lyrics fit the notes but not about the appearance.

Stave 1 of the attached file shows examples of grace notes with and without slurs; all notes have the attribute Lyric:Default.

Stave 2 shows what happens when this is exported to a v1.75 file. The middle notes in bars 2 and 4 now have the attribute Lyric:Never and the slur in bar 3 is missing.

Stave 3 has been doctored by modifying the Lyric attribute of three notes as indicated by the text notes.

Stave 4 confirms that, when this is exported, the lyrics are correct, although the slur in bar 3 is still missing.

Is there a good reason why export from NWC2 behaves in this way?
10
General Discussion / Re: Overlaying parts
Yes indeed, some problems can be resolved with stems up/down but that is not always desirable.

Here is a little sample of two parts on one stave. NWC resolves all the conflicts.
Code: (nwc) [Select · Download]
!NoteWorthyComposerClip(2.0,Single)
|Clef|Type:Treble
|Key|Signature:F#
|TimeSig|Signature:2/4
|Text|Text:"two parts on one stave"|Font:StaffItalic|Pos:9
|Rest|Dur:8th
|Chord|Dur:8th|Pos:n-3,-1|Opts:Stem=Up
|Chord|Dur:8th|Pos:#-6,-3|Opts:Stem=Up,Beam=First
|Chord|Dur:8th|Pos:-6,-4|Opts:Stem=Up,Beam=End
|Bar
|Chord|Dur:4th|Pos:b-5,b-4
|Chord|Dur:8th|Pos:-1,0|Opts:Stem=Up,Beam=First
|Chord|Dur:8th|Pos:-1,0|Opts:Stem=Up,Beam=End
!NoteWorthyComposerClip-End

If I put the two parts on separate staves (eg using adp's very useful Parts tool) and simply overlay them, there are stem conflicts with the quaver notes, accidental conflicts and note head conflicts. All of these can be resolved with some rather painstaking adjustment; for example here are two separate staves which, when overlayed, will print correctly and play correctly, allow the audio of the parts to be controlled separately but not require anything to be duplicated or dictate stem directions.
Upper part:
Code: (nwc) [Select · Download]
!NoteWorthyComposerClip(2.0,Single)
|Clef|Type:Treble
|Key|Signature:F#
|TimeSig|Signature:2/4
|Text|Text:"manually adjusted and overlayed"|Font:StaffItalic|Pos:9
|Rest|Dur:8th
|Note|Dur:8th|Pos:-1|Opts:Stem=Up
|Note|Dur:8th|Pos:-3|Opts:Stem=Up,Beam=First
|Note|Dur:8th|Pos:-4|Opts:Stem=Up,Beam=End
|Bar
|Note|Dur:4th|Pos:b-4|Opts:StemLength=0,XNoteSpace=1
|Note|Dur:8th|Pos:0|Opts:Stem=Up,StemLength=0,Beam=First,XNoteSpace=1
|Note|Dur:8th|Pos:0|Opts:Stem=Up,StemLength=0,Beam=End,XNoteSpace=1
!NoteWorthyComposerClip-End
Lower part:
Code: (nwc) [Select · Download]
!NoteWorthyComposerClip(2.0,Single)
|Clef|Type:Treble
|Key|Signature:F#
|TimeSig|Signature:2/4
|Rest|Dur:8th
|Note|Dur:8th|Pos:n-3|Opts:Stem=Up,StemLength=9
|Note|Dur:8th|Pos:#-6|Opts:Stem=Up,StemLength=10,Beam=First
|Note|Dur:8th|Pos:-6|Opts:Stem=Up,StemLength=9,Beam=End
|Bar
|Note|Dur:4th|Pos:b-5|Opts:XAccSpace=1
|Note|Dur:8th|Pos:-1|Opts:Stem=Up,Beam=First
|Note|Dur:8th|Pos:-1|Opts:Stem=Up,Beam=End
!NoteWorthyComposerClip-End

 So it can be done, but with a lot of trouble to replicate what NWC does. I was considering whether this process can be automated.
11
General Discussion / Overlaying parts
I use NWC mostly for SATB choral works. If two parts share a stave, NWC does the right thing with visual conflicts, eg position of notes that are close in pitch or stem lengths of quavers etc.. However for learning parts it is more helpful to have each part on its own stave but overlay them; now I have to resolve the conflicts manually by adjusting stem lengths (sometimes to zero) or adding note spacing where required.

It is of course possible to have two sets of staves - one visible but muted, the other undisplayed but audible - but that makes editing tedious and there is a danger of discrepancies creeping in between the two sets.

Does anyone have ideas of how to capture NWC's musical intelligence, for example by deleting one note in a chord but retaining any special treatment given to the other?

-- Brian Maskell
12
User Tools / Re: Imported triplets
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
13
User Tools / Re: Imported triplets
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 · Download]
$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
15
User Tools / Re: Imported triplets
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 · Download]
<?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);

?>
16
User Tools / Re: Imported triplets
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
17
User Tools / Re: Imported triplets
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
18
User Tools / Re: Imported triplets
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 · Download]
// 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?
19
User Tools / 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 · Download]
<?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);

?>
20
User Tools / Re: Documentation for the User Tools API
Lawrie,

Indeed, I have found "Variable Dump for Developers (nwsw)" very instructive but of course it only shows the elements I have included in my clip. No doubt I shall discover them all eventually; I was hoping for a quicker and more reliable route.

-- Brian
21
User Tools / Documentation for the User Tools API
Is there a detailed and comprehensive document describing the User Tools Application Programming Interface?

I am slowly getting the hang of it but I keep encountering new (to me) elements; ArticulationsOnStem is the my latest and in this case I still havn't worked out when it occurs and what effect it has.

On this basis, writing robust User Tool code is a rather uncertain business.

-- Brian