summaryrefslogtreecommitdiff
path: root/Build/source/utils/m-tx/mtx-src/MAINTENANCE
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/utils/m-tx/mtx-src/MAINTENANCE')
-rw-r--r--Build/source/utils/m-tx/mtx-src/MAINTENANCE278
1 files changed, 278 insertions, 0 deletions
diff --git a/Build/source/utils/m-tx/mtx-src/MAINTENANCE b/Build/source/utils/m-tx/mtx-src/MAINTENANCE
new file mode 100644
index 00000000000..3fa6459e86c
--- /dev/null
+++ b/Build/source/utils/m-tx/mtx-src/MAINTENANCE
@@ -0,0 +1,278 @@
+Notes for maintainers of M-Tx
+=============================
+
+This file is for someone who wishes to fix a bug, without actually being
+willing to master the whole program of well over 4000 lines spread over
+16 files.
+
+1. Quick start
+--------------
+
+The main program is `prepmx.pas`. The other files are units.
+
+There are three places in each file that give a lot of useful information.
+When you have a spare moment, look at those. It will save time later.
+
+1. Immediately after the first line, there should be a comment
+ containing a short description of what is in the file.
+2. After the keyword `interface`, there may be a `uses` statement
+ listing the units needed in order that the interface (the Pascal
+ equivalent of a C header file) can be defined.
+3. After the keyword `implementation`, there may be a `uses` statement
+ listing the further units needed in order that the implementation
+ (the equivalent of a C code file) can be defined.
+
+Instead of some dull generalities, here is a blow-by-blow account of how an
+actual bug reported by Wojtek Chemijewski on the Werner Icking Music Archive
+mailing list `<tex-music@tug.org>` on 2014-01-29 was found and "corrected".
+
+2. Case study
+-------------
+
+I'm doing this from a command line in Ubuntu Linux, so I use `grep`.
+Any other tool that can search for text in the Pascal source files
+will do equally well.
+
+In M-Tx 0.60, the following behaviour was considered to be a bug:
+
+> If the piece starts with a pickup, then the bar separator at the end
+of the pickup is only recognized as terminating a pickup when it is
+a plain bar line `|`, not when it is a left repeat `|:`.
+
+There are workarounds for this, the best one being to not use plain bar
+lines but to use the PMX command `Rl` in the lowest voice (which becomes
+the first voice in PMX). That is not the point. We want M-Tx to do this
+right.
+
+We start by looking for the string `|:` in all the source files.
+
+ $ grep '|:' *.pas
+ prepmx.pas: else if bar='|:' then repcode:='Rl'
+ prepmx.pas: else if bar=':|:' then repcode:='Rlr'
+
+(BTW, although I continue using `grep`, I won't again insult your
+intelligence by quoting the exact invocation.)
+
+On reading the source, we note that both references are inside
+a procedure called `writeRepeat`.
+
+ procedure writeRepeat(var bar: string);
+ var repcode: string;
+ begin if bar='' then exit;
+ repcode:='';
+ if bar='||' then repcode:='Rd'
+ else if (bar='|]') then repcode:='RD'
+ else if bar='|:' then repcode:='Rl'
+ else if bar=':|:' then repcode:='Rlr'
+ else if bar=':|' then repcode:='Rr'
+ else if last_bar and (bar='|') then repcode:='Rb';
+ if repcode<>'' then putLine(' '+repcode);
+ bar:='';
+ end;
+
+This clearly writes to the PMX file the PMX code to which the M-Tx
+bar symbol translates. It can't be the cause of the bug.
+
+Where is `writeRepeat` used? Another `grep` says: only in `prepmx.pas`
+itself, four times.
+
+The first one is in the following context:
+
+ barword: begin
+ if voice=nvoices then
+ if endOfBar(voice,bar_no) then repeat_sign := note
+ else writeRepeat(note);
+ if note<>barsym then note:='';
+ no_chords:=false;
+ end;
+
+This code tests whether we are at the end of a bar; if so, the bar
+word is remembered as `repeat_sign`, otherwise it is written
+immediately. We are following a breadth-first strategy, so we don't
+yet look at `endOfBar`, but continue with examining the references
+to `writeRepeat`.
+
+The second reference in context:
+
+ if bar_of_line>1 then putLine(comment+'Bar '+toString(bar_no));
+ last_bar := (bar_of_line=nbars) and final_paragraph;
+ if last_bar and (repeat_sign='|') then repeat_sign:='';
+ writeRepeat(repeat_sign); new_meter := '';
+
+This occurs in a procedure called `processOneBar`. The first thing is
+to write out the `repeat_sign` previously saved. It also does not
+seem to be the cause of the bug.
+
+The third reference is inside a loop over all voices. If that is the
+problem, we may have to dig very deeply, so we postpone looking at it.
+
+The last reference is inside the procedure `doMusic`.
+
+ if startsWithBracedWord(P[1]) then lyricsParagraph else
+ begin musicParagraph; first_paragraph:=false;
+ writeRepeat(repeat_sign);
+ end
+
+That boolean `first_paragraph` sticks out like a sore thumb. The
+program is doing something differently, depending on whether we are
+doing the first paragraph or a later one! That cannot be unrelated
+to the question of a pickup.
+
+Let's look for where `first_paragraph` is used. In `files.pas` there
+is something that appears to be handling inline TeX, the others are
+in `prepmx.pas`. It appears in this conditional:
+
+ if first_paragraph then includeStartString;
+
+Let's find that procedure. It's in `analyze.pas`. The procedure itself
+is clearly blameless, but also near the top of `analyze.pas` our eye
+alights on `pickup:=0;`. The statement appears twice, the second time
+probably needlessly, but that's beside the point. We now know there is
+a variable named `pickup` and unless the author of the program is
+deliberately misleading us, it must be relevant to the pickup.
+
+Further on in `analyze.pas`, we find:
+
+ scanMusic(voice,l);
+ if multi_bar_rest and (nv>1) then error(
+ 'Multi-bar rest allows only one voice',print);
+ if not pmx_preamble_done then
+ if voice=top then pickup:=l
+ else if pickup<>l then
+ error3(voice,'The same pickup must appear in all voices');
+
+I.e. the procedure `scanMusic` sets a value `l` that will be assigned
+to `pickup`. We're getting warm!
+
+That procedure is in `mtx.pas`. It is a very long procedure, but the
+argument `l` is now the parameter `left_over`, easy to search for.
+The only place where it seems to be used after being initialized is
+in the following compound statement:
+
+ if (note=barsym) then
+ if meternum=0 then
+ error3(voice,'You may not use bar lines in barless music')
+ else if bar_length=0 then markBar(voice)
+ else if (numberOfBars(voice)=0) and (bar_length<bar) then
+ begin if has_next then
+ has_next:=false {Should check whether pickups are equal}
+ else if left_over>0 then error3(voice,'Bar is too short');
+ left_over:=bar_length; bar_length := 0;
+ end;
+
+What is `barsym`? These are the places where the name appears.
+
+ analyze.pas: is_labelled := (w[l]=colon) and (w[l-1]<>barsym);
+ globals.pas: barsym = '|';
+ mtxline.pas: if chord>0 then if P[chord]=barsym then predelete(P[chord],1);
+ mtx.pas: if (note=barsym) then
+ prepmx.pas: if (w=barsym) or (w='') then no_chords:=true;
+ prepmx.pas: if note<>barsym then note:='';
+ uptext.pas: if (w=barsym) or (w='') then no_uptext:=true;
+
+Our quest is over! This is the bug. The whole code starts with
+`if (note=barsym) then`. The parentheses indicate that at some
+previous stage of the code, the programmer thought that there may
+have been extra clauses, such as testing for left repeat, but forgot
+to put them in. We also note further evidence of not-quite-completed
+code in the comment `{Should check whether pickups are equal}`.
+
+How will we fix this? Should we have `if isBarSym(note) then` and supply
+a function `isBarSym`, or merely add a clause? It appears as if the
+other cases do not require the same fixing, so the function will only be
+called this once. Let's try:
+
+ if note[1]=barsym then
+
+That will cover `|`, `|:` and `||` but not `:|`, which seems fair
+enough. It will also cover illegal cases like `|-`, but let's hope
+those are caught elsewhere.
+
+The change is enough to cure the reported bug, so we document it at the
+top of `prepmx.pas` and in `Corrections`, changing the version number to
+`0.60e`.
+
+### It was not a bug
+
+After carrying out the above exercise, I carefully reread the M-Tx
+manual and found:
+
+> If your piece starts with a pickup, it is defined by a bar line at the
+> end of “bar 0”, i.e. the incomplete bar containing the pickup. Even
+> when there is a repeat sign after the pickup, so that you don’t
+> actually see a bar line in the printed music, you still need a bar
+> line before the repeat sign, otherwise M-Tx cannot know where the
+> pickup stops. The bar line defining the pickup is compulsory in the
+> first voice found, and optional but recommended in the others.
+
+Be warned, therefore, that M-Tx 0.60e is a dead end. The next version
+of M-Tx will branch from 0.60d again.
+
+3. Adding a preamble command
+----------------------------
+
+In M-Tx 0.61, the `TeX` preamble command was added. Obviously we start
+in `preamble.pas`. There are some pretty obvious lists contained in
+`type` and `const` statements.
+
+The name lower-case name `tex` is inserted in the list of names
+`command_type`, the upper case version `TEX` in the corresponding
+position in `commands`, a default empty value in `cline` and an
+extra `false` in `redefined`. This is enough to get the `TeX` command
+recognized, to save the value given, and to record the fact that there
+was a change.
+
+We want the TeX string to appear immediately after `readmod`. If you
+examine a `.tex` file made by PMX from source coded by M-Tx, you will
+see that `\mtxInterInstrument` is there. The only line containing that
+word is in `preamble.pas`:
+
+ if nspace[j]<>unspec then TeXtype2('\mtxInterInstrument{'+toString(i-1)+
+
+I.e. it was inserted as a "Type 2 TeX string". The line in question is
+inside the function `respace`, which we see is executed immediately
+after the statement `pmx_preamble_done:=true;`. We need to squeeze
+in between, adding a call
+
+ insertTeX;
+
+At some respectable point before that, we define this procedure.
+
+ procedure insertTeX;
+ begin
+ if redefined[tex] then TeXtype2(cline[tex]);
+ end;
+
+Test it; it works. However, if we issue more than one `TeX` command,
+only the last one takes effect, whereas the user will want all.
+Looking for assignments into `cline`, we find in procedure `doCommand`
+the statement:
+
+ cline[last_command]:=line;
+
+The entire loop containing that statement needs a bit of reorganization,
+since we must not only allow for appending new text but also bypass the
+test for redefinition warnings. First we supply a function
+
+ function mustAppend(command: command_type): boolean;
+ begin mustAppend := command=tex end;
+
+which allows for possible future preamble commands that also append
+rather than redefine. The new code of the loop starting with
+
+ if mustAppend(last_command) and redefined[last_command] then
+
+is straightforward, almost. Do we put in `LineEnding` when we append?
+If we don't, we may run into problems with `PMXlinelength` which is
+only 128. If we do, `TeXtype2` needs to be made a little smarter,
+i.e. able to re-split the lines. Actually, in that case an explicit
+`#10` is better, since on some systems `LineEnding` has two characters.
+
+`TeXtype2` is in `files.pas`. There is a line
+
+ else if first_paragraph then putLine('\'+s+'\')
+
+that we replace by a call to a routine `putTeXlines` which does
+the necessary.
+
+