package eu.bandm.music.haken ; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.io.File; import eu.bandm.tools.annotations.Opt ; import eu.bandm.tools.util.Rational ; import eu.bandm.tools.message.SimpleMessage; import eu.bandm.tools.message.MessageReceiver; import eu.bandm.tools.message.MessagePrinter; import eu.bandm.tools.message.MessageFormatter; import eu.bandm.tools.message.MessageCounter; import eu.bandm.tools.message.MessageTee; import eu.bandm.tools.message.XMLDocumentIdentifier; import eu.bandm.tscore.model.TimeScape ; import eu.bandm.tscore.model.Part ; import eu.bandm.tscore.model.Tp ; import eu.bandm.tscore.model.Vox ; import eu.bandm.tscore.model.Event ; import eu.bandm.tscore.base.Util ; import eu.bandm.music.entities.RationalDuration.DottedBaseDuration ; import eu.bandm.music.entities.QualifiedRational ; import eu.bandm.music.entities.MetricConsumer ; import eu.bandm.music.entities.MTree ; import eu.bandm.music.entities.StemEnd ; /** A first version of a haken score for realization by a viola solo. * This is the production version for one single output instrument "Vla" with * one or two K-voices per parameter * = "th" (pitch), "dur" (duration) and "ls" (intensity).
* Usage: create an Instance (with an already raw-parsed TimeScape) * and call "update(... score.findVoice(..)..).
* The {@link #main(String[])} method takes filenames and voice names from the command * line parameters. */ public class Score_hkn_vla_v00 extends Score_hkn { // --------------------------------------------------------------------- // lilypond conversions // --------------------------------------------------------------------- // pause check 0<=p<=4 MISSING FIXME /** Is inserted as voice name into the generated LilyPond source.*/ public static final String instrumentname = "Vla" ; // ALT: // U M O // 2*1/32 3/4 FERMATE // NEU: // 0 1 // 2*1/32 3/16 1/2 5/4 FERMATE /** Evident.*/ protected static final Rational rat_1_32 = Rational.valueOf(1,32); /** Evident.*/ protected static final Rational rat_1_16 = Rational.valueOf(1,16); /** Evident.*/ protected static final Rational rat_3_16 = Rational.valueOf(3,16); /** Evident.*/ protected static final Rational rat_1_2 = Rational.valueOf(1,2); /** Evident.*/ protected static final Rational rat_3_4 = Rational.valueOf(3,4); /** Evident.*/ protected static final Rational rat_5_4 = Rational.valueOf(5,4); /** Evident.*/ protected static final QualifiedRational sound_1_32 = QualifiedRational.sound(rat_1_32); /** Evident.*/ protected static final QualifiedRational pause_1_32 = QualifiedRational.pause(rat_1_32); /** Evident.*/ protected static final QualifiedRational sound_3_16 = QualifiedRational.sound(rat_3_16); /** Evident.*/ protected static final QualifiedRational sound_1_2 = QualifiedRational.sound(rat_1_2); /** Evident.*/ protected static final QualifiedRational sound_3_4 = QualifiedRational.sound(rat_3_4); /** Evident.*/ protected static final QualifiedRational sound_5_4 = QualifiedRational.sound(rat_5_4); // --------------------------------------------------------------------- /** Only constructor. * @param name of the score, also used to derive output file names * @param ts the raw parsed tscore input data. * @param msg the drain of all messages. * @param parameters controll parsing, if left out, derfaults to "new {@link Parameters}". */ public Score_hkn_vla_v00 (final String name, final Part ts, final MessageReceiver> msg, final @Opt Parameters parameters){ super(name, ts, msg, parameters); lilypondGenerator = new LilypondGenerator(msg); } /** FIXME DOC*/ public static Map fromTimeScape (final TimeScape ts, final String topName, final MessageReceiver> msg, final @Opt Parameters parameters){ return Util.partsToScores(ts, topName, "%s_%s", msg, parameters, Score_hkn_vla_v00::new); } /** Auxiliary module for generating a LilyPond source text as output.*/ protected final LilypondGenerator lilypondGenerator ; /** Additional lilypond source text which defines the macros for "highest possible pitch".*/ protected String specialPreamble = "\n" +"triHead = {\n" +" \\override NoteHead #'stencil = #ly:text-interface::print\n" +" \\override NoteHead #'text = \\markup {\n" +" \\postscript #\" 0.2 setlinewidth\n" +" 0 -1.2 rmoveto" +" -1.2 0 rlineto 1.2 1.2 rlineto 1.2 -1.2 rlineto \n" +" -1.2 0 rlineto stroke\" }\n" +"}\n" +"triHeadBlack = {\n" +" \\override NoteHead #'stencil = #ly:text-interface::print\n" +" \\override NoteHead #'text = \\markup {\n" +" \\postscript #\" 0.2 setlinewidth\n" +" 0 -1.2 rmoveto" +" -1.2 0 rlineto 1.2 1.2 rlineto 1.2 -1.2 rlineto \n" +" -1.2 0 rlineto fill\" }\n" +"}\n" +"triHoff = { \\revert NoteHead #'stencil }\n"; /** The last written out intensity value, for eliminating repetitions in the * generated LilyPond source */ protected Integer currentIntensity = null ; /** Whether the predecessor event of a fermata (sound or pause) has just changed the * meter to an incomplete measure <4/4. */ protected boolean timeChanged ; /** The last sounding pitch, needed to check if a "breath" symbol has to be * entered in the generated LilyPond source text. */ protected Integer thBreath = null ; /** Index how fat the generated lists of indexes have been written out. */ protected int accu_index ; /** The index of K-Events when the MetricConsumer steps through the list of * pauses and durations. (Must be a class-level field only for Java-technical reaosns.) */ protected int accu_index_writeout ; /** Whether the last emitted lilypoind V-event was a pause. * (Must be a class-level field only for Java-technical reaosns.) */ boolean wasPause = false ; /** List of indexes into the value array for pitches {@link #relwert_2_pitch}. * Constant 1 if no K-voice is selected, 0..2 for one K-voice and 0..4 for two. */ List seq_pitch ; /** List of indexes into the value array for durations {@link #relwert_2_d?????pitch}. * Constant 1 if no K-voice is selected, 0..2 for one K-voice and 0..4 for two. */ List seq_duration ; /** List of indexes into the value array for dynamics {@link #relwert_2_intensity}. * Constant 1 if no K-voice is selected, 0..2 for one K-voice and 0..4 for two. */ List seq_intensity ; /** LilyPond source text framgents for intentities 0..4*/ protected String relwert_2_intensity (final Integer r){ switch (r){ case 0 : return "\\pppp" ; case 1 : return "\\p" ; case 2 : return "\\mf" ; case 3 : return "\\f" ; case 4 : return "\\ffff" ; } throw new RuntimeException("invalid intensity constants array index "+r); } /** LilyPond source text framgents for pitches 0..4*/ protected String relwert_2_pitch (final Integer r){ switch (r){ case 0 : return "c" ; case 1 : return "h" ; case 2 : return "a'" ; case 3 : return "gis''" ; case 4 : return "h''" ; } throw new RuntimeException("invalid pitch constants array index "+r); } /** Highest index into the constant value arrays.*/ final int indexMax = 4 ; /** Medium index into the constant value arrays.*/ final int indexMed = 2 ; /** Duration values for the pauses p0..p4 */ QualifiedRational[] pauseGrades = { QualifiedRational.pause(1,4), QualifiedRational.pause(3,4), QualifiedRational.pause(5,4), QualifiedRational.pause(8,4), QualifiedRational.pause(12,4) }; /** Highest index into the constant pause durations arrays.*/ final public int maxPause = pauseGrades.length-1 ; /** Show one staff with output notes for the viola and changing meters caused by * whole-measure fermatas. *
* Add all duration values. Whenever a "duration=maximal" is reached, * flush_accu() is called (which may generate a partial measure). * Then a fermate measure is created. In any case the durations < 1/1 are * split according to 4/4 meter. */ protected void writeToFormat (final Expansion exp, final @Opt Vox vpause /* glo in seq_pitch, seq_duration, glo in out ... */){ final int len = seq_pitch.size(); assert len == seq_duration.size() : "pitch and duration list length differ" ; assert len == seq_intensity.size() : "pitch and intensity list length differ" ; lilypondGenerator.open_staff(instrumentname, instrumentname); lilypondGenerator.putS("\\clef alto"); lilypondGenerator.putS("\\time 4/4"); currentIntensity = null ; timeChanged = false ; accu_index = 0 ; // = where to start flushing for (int i = 0 ; i tps, final @Opt Vox vpause, final int i /*GLO IN pauseGrades */){ final int g = getPauseIndexByIndex (tps, vpause, i, maxPause ); return (g>noPauseSelected) ? pauseGrades[g] : null ; } /** Generate the lilypond text for dynamics. * Reads and writes {@link #currentIntensity}. * @param eventSoFar source text built so far, to which append the dynamics, if necessary. * @param intens index into {@link * @return eventSoFar with added suffix. */ protected String change_intensity (final String eventSoFar, final @Opt Integer intens){ if (intens==null) return eventSoFar ; if (intens==currentIntensity) return eventSoFar ; currentIntensity = intens ; return eventSoFar+relwert_2_intensity(intens); } /** Extends the event if a breath-symbol is required before (i.e. dynamics and pitch * do repeat), and stores the event-pitch as new last-pitch. */ protected String checkForThBreath (final String eventSoFar, final Integer th, final Integer intens /*glo in out thBreath glo in wasPause */ ){ if (thBreath==null || wasPause || (intens!=null && intens!=currentIntensity)) { thBreath = th ; return eventSoFar ; } final Integer oldTh = thBreath ; thBreath = th ; if (oldTh!=thBreath) return eventSoFar ; return eventSoFar+"\\breathe " ; } /** * Since a new 4/4 measure is started with every fermata-event (duration="O"="O+O"=5), * all events up to such a "maxDuration" event are accumulated and then * rendered by this method. * The accumulator is realized by global accu_index=start and parameter "max"=exclusive end. *
   *              accu_index      max
   *              v               v
   *  tp4    5    6     7    8    9   10 ...
   * generate:
   *              p===  p--  p==  P
   *  
* Generate the events and the pauses. Pauses in the K-Voice are meant BEFORE the event. * But for generating the Graphic output afterwards, their duration is added to the * predecessor event in "allDurations". *
* The possible pause before the "maxDuration" must be realized here, AFTER the "max-1" event * @param checkLastForPause whether FIXME */ protected void flush_accu(final int max, final List tps, final Vox vpause, final boolean checkLastForPause /*GLO IN accu_index, etc.*/ ){ final QualifiedRational lastPause = checkLastForPause ? getPauseByIndex(tps, vpause, max) : null ; if (accu_index==max && lastPause==null) return ; // nothing to flush // Build a list of durations which correspond to the K-events accu_index to max. // To each K-Event corresponds a sequence of ONE sound and ZERO TO MANY pauses. // These will be translated later to LilyPond in the metric consumer WriteOut loop. final List durations = new ArrayList<>(); for (int i = accu_index; i1) lil_event +="\\triHeadBlack "; else lil_event +="\\triHead "; needsInit=false ; } if (isFirst) lil_event = checkForThBreath(lil_event, rel_pitch, rel_intens); lil_event +=text_pitch ; lil_event += lilypondGenerator.lilypond_duration_encoding(dbd); if (!isLast) lil_event+=lilypondGenerator.lilypond_tie_symbol ; lil_event = change_intensity (lil_event, rel_intens); if (isFirst){ final Tp now = tps.get(accu_index_writeout); if (tp2barnum.containsKey(now)) lil_event+="^\\markup{\\small{("+tp2barnum.get(now)+")}}"; } lil_event += " " ; if (isLast && rel_pitch==indexMax/*RelWert.O*/) lil_event +="\\triHoff "; if (isLast) accu_index_writeout++; }//sound lilypondGenerator.putF(lil_event); wasPause=!sound; } @Override public void open_proportion (Rational proportion){ throw new IllegalStateException("shall not happen."); } @Override public void close_proportion (Rational proportion){ throw new IllegalStateException("shall not happen."); } }; //class extends WriteOut writeOut.process(); lilypondGenerator.putF(" |"); }//mc.hasNext }//flush_accu /** Generate a fermata-measure for event number "i".*/ protected void put_long_event(final int i, final List tps /*GLO OUT subs, GLO IN ..*/){ if (timeChanged) lilypondGenerator.putS(" \\time 4/4"); lilypondGenerator.putF("%% %d", i); lilypondGenerator.putS(" |"); final Integer rel_pitch = seq_pitch.get(i); final String text_pitch = relwert_2_pitch(rel_pitch); final Integer rel_intens = seq_intensity.get(i); lilypondGenerator.startGraphEvent(Rational.ONE); // das breathe wird VOR die Note gesetzt, genau wie oben: String event = checkForThBreath("", rel_pitch, rel_intens); event += text_pitch+"1\\fermata" ; final Tp now = tps.get(i); if (tp2barnum.containsKey(now)) event+="^\\markup{\\small{("+tp2barnum.get(now)+")}}"; event = change_intensity (event, rel_intens); if (rel_pitch==indexMax) event = "\\triHead " + event +" \\triHoff "; lilypondGenerator.putF(event); wasPause = false ; }//put_long_event /** Generate a fermata-measure for longest possible pause.*/ protected void put_long_pause(/*GLO OUT subs, GLO IN ..*/){ if (timeChanged) lilypondGenerator.putS(" \\time 4/4"); lilypondGenerator.putS("R1\\fermata \bar\"||\" "); wasPause = true ; }//put_long_event /** Convert the RelWert of the K-Voices into a list of array index values. * By {@link RelWert#getArrayIndex()}. *
   *  exp1!=null eep2!= null =>    add them 
   *  exp1!=null eep2== null =>    put only voice 1 
   *  exp1==null eep2!= null =>    put only voice 2 and emit warning 
   *  exp1==null eep2== null =>    put sequence of "M" values
   *  
* The same voice(-name) can be given twice, what means a doubling of the * voice's V-effects. */ protected void addValues (final @Opt List exp1, final @Opt List exp2, final int len, final String p, final /*out*/ List out){ if (exp1!=null) if (exp2==null) for (int i = 0; i(limit); seq_duration = new ArrayList(limit); seq_intensity = new ArrayList(limit); addValues(exp.expanded.get(v_dt0), exp.expanded.get(v_dt1), limit, "dt", seq_duration); addValues(exp.expanded.get(v_th0), exp.expanded.get(v_th1), limit, "th", seq_pitch); addValues(exp.expanded.get(v_ls0), exp.expanded.get(v_ls1), limit, "ls", seq_intensity); lilypondGenerator.open(inputFileName, outputFileStem, subtitle); lilypondGenerator.putText(specialPreamble); lilypondGenerator.open_score(); lilypondGenerator.open_staffGroup(); // ===================================== writeToFormat (exp, v_ps /*, glo seq_duration...*/); // ===================================== lilypondGenerator.putSpace(); // ===================================== writeVoiceGraphics (exp, "dura", v_dt0); if (v_dt0!=v_dt1) writeVoiceGraphics (exp, "dura", v_dt1); writeVoiceGraphics (exp, "tonh", v_th0); if (v_th0!=v_th1) writeVoiceGraphics (exp, "tonh", v_th1); writeVoiceGraphics (exp, "laut", v_ls0); if (v_ls0!=v_ls1) writeVoiceGraphics (exp, "laut", v_ls1); // ===================================== lilypondGenerator.close_staffGroup(); lilypondGenerator.close_score(); } protected void writeVoiceGraphics (final Expansion exp, final String title, final @Opt Vox v){ if (v==null) return ; lilypondGenerator.writeGraphics(title, v, exp.expanded.get(v), exp.tps, event2relwert, event2haken, event2doppelHaken); } // --------------------------------------------------------------------- /** Main method, translates input file in HKN-vox format to lilypond output file. * @param args[0] input file name * @param args[1] basic score name (also determines the output files) * @param args[2] used for the entry catgory "subtitle" in the header of the * generated lilypond source. "-" stands for "none", * (The category "title" will be filled by the name of the outputfile, * "subsubtitle" will be a source file indication.) * @param args[3] pause voice ("-" stands for "none") * @param Args[4]/[5] give the two voices for "Dauer" ("duration") * @param args[6]/[7] give the two voices for "Tonhoehe" ("pitch") * @param args[8]/[9] give the two voices for "Lautstärke" ("volume"/"intensity") */ public static void main (String[] args) { final MessageCounter mcnt = new MessageCounter(); final MessageReceiver> msg = new MessageTee<>(new MessageFormatter<>(new MessagePrinter<>()), mcnt); if (args.length<6) msg.receive(SimpleMessage.error("not enough command line parameters.")); mcnt.terminateApplicationOnErrors(); String message = null ; final String inputfilename = (args[0]); msg.receive(SimpleMessage.logStart("parsing input file %s to raw data", inputfilename)); final File inputfile = new File(inputfilename) ; final TimeScape ts = Util.parseTimeScape(inputfile, modifiers, msg); msg.receive(SimpleMessage.logEnd("parsing input file")); mcnt.terminateApplicationOnErrors(); // ==================================================================== // convert raw model into Score_haken semantic interpretation // ==================================================================== msg.receive(SimpleMessage.logStart(message="semantic interpretation")); final String scoreNameStem = args[1]; final Map scores = fromTimeScape(ts,scoreNameStem,msg,null); String subtitle = args[2]; if (noVoiceSelected.equals(subtitle)) subtitle = null ; for (final Score_hkn_vla_v00 score : scores.values()){ final Vox vps = score.findVoice (args[3], true/*=errorNotWarning on spelling error.*/); // from here different from "vox": final @Opt Vox vdt0 = score.findVoice (args[4], true); final @Opt Vox vdt1 = score.findVoice (args[5], true); final @Opt Vox vth0 = score.findVoice (args[6], true); final @Opt Vox vth1 = score.findVoice (args[7], true); final @Opt Vox vls0 = score.findVoice (args[8], true); final @Opt Vox vls1 = score.findVoice (args[9], true); mcnt.terminateApplicationOnErrors(); // ====================================================== score.update(inputfilename, score.name, subtitle, vps, vdt0, vdt1, vth0, vth1, vls0, vls1); // ====================================================== msg.receive(SimpleMessage.logEnd(message)); // ==================================================================== // write lilypond format objects to output file // ==================================================================== final String outputfilename = score.name+".ly"; score.lilypondGenerator.writeToFile(outputfilename); msg.receive(SimpleMessage.log("result written to %s", outputfilename)); }//for scores }//main }