diff --git a/midica.jar b/midica.jar index c5049d8..fd9b7a5 100644 Binary files a/midica.jar and b/midica.jar differ diff --git a/src/org/midica/Midica.java b/src/org/midica/Midica.java index 179d026..d6db20a 100644 --- a/src/org/midica/Midica.java +++ b/src/org/midica/Midica.java @@ -36,7 +36,7 @@ public class Midica { private static final int VERSION_MINOR = 11; /** UNIX timestamp of the last commit */ - public static final int COMMIT_TIME = 1704405921; + public static final int COMMIT_TIME = 1705915601; /** Branch name. Automatically changed by precommit.pl */ public static final String BRANCH = "sound-effects"; diff --git a/src/org/midica/config/Dict.java b/src/org/midica/config/Dict.java index f88afac..e27cb08 100644 --- a/src/org/midica/config/Dict.java +++ b/src/org/midica/config/Dict.java @@ -250,6 +250,8 @@ public class Dict { public static final String SYNTAX_CC_5D_EFF3_DEP = "CC_5D_EFF3_DEP"; public static final String SYNTAX_CC_5E_EFF4_DEP = "CC_5E_EFF4_DEP"; public static final String SYNTAX_CC_5F_EFF4_DEP = "CC_5F_EFF4_DEP"; + public static final String SYNTAX_CC_7E_MONO_MODE = "CC_7E_MONO_MODE"; + public static final String SYNTAX_CC_7F_POLY_MODE = "CC_7F_POLY_MODE"; public static final String SYNTAX_RPN_0_PITCH_BEND_R = "RPN_0_PITCH_BEND_R"; public static final String SYNTAX_RPN_1_FINE_TUNE = "RPN_1_FINE_TUNE"; public static final String SYNTAX_RPN_2_COARSE_TUNE = "RPN_2_COARSE_TUNE"; @@ -1757,6 +1759,9 @@ public class Dict { public static final String ERROR_FL_CONT_RPN = "error_fl_cont_rpn"; public static final String ERROR_FL_CONT_NRPN = "error_fl_cont_nrpn"; public static final String ERROR_FL_NOTE_NOT_SUPP = "error_fl_note_not_supp"; + public static final String ERROR_FL_NOTE_NOT_SET = "error_fl_note_not_set"; + public static final String ERROR_FL_NOTE_PAT_IDX_NAN = "error_fl_note_pat_idx_nan"; + public static final String ERROR_FL_NOTE_PAT_IDX_TOO_HIGH = "error_fl_note_pat_idx_too_high"; public static final String ERROR_FUNC_TYPE_NOT_BOOL = "error_func_type_not_bool"; public static final String ERROR_FUNC_TYPE_BOOL = "error_func_type_bool"; public static final String ERROR_FUNC_TYPE_NONE = "error_func_type_none"; @@ -3018,6 +3023,8 @@ private static void initLanguageEnglish() { set( SYNTAX_CC_5D_EFF3_DEP, "Controller 93 (0x5D): Effect 3 Depth (Chorus)" ); set( SYNTAX_CC_5E_EFF4_DEP, "Controller 94 (0x5E): Effect 4 Depth (Celeste/Detune)" ); set( SYNTAX_CC_5F_EFF4_DEP, "Controller 95 (0x5F): Effect 5 Depth (Phaser)" ); + set( SYNTAX_CC_7E_MONO_MODE, "Controller 95 (0x7E): Mono Mode ON (Poly OFF)" ); + set( SYNTAX_CC_7F_POLY_MODE, "Controller 95 (0x7F): Poly Mode ON (Mono OFF)" ); set( SYNTAX_RPN_0_PITCH_BEND_R, "RPN 0 (0x0000): Pitch Bend Range" ); set( SYNTAX_RPN_1_FINE_TUNE, "RPN 1 (0x0001): Channel Fine Tune" ); set( SYNTAX_RPN_2_COARSE_TUNE, "RPN 2 (0x0002): Channel Coarse Tune" ); @@ -3571,6 +3578,9 @@ private static void initLanguageEnglish() { set( ERROR_FL_CONT_RPN, "Effect function '%s' is not allowed for RPN-based effects." ); set( ERROR_FL_CONT_NRPN, "Effect function '%s' is not allowed for NRPN-based effects." ); set( ERROR_FL_NOTE_NOT_SUPP, "A note is not supported for the chosen effect type. Invalid element: " ); + set( ERROR_FL_NOTE_NOT_SET, "The chosen effect needs a note to be set before you can use this function: " ); + set( ERROR_FL_NOTE_PAT_IDX_NAN, "pattern index '%s' is not a number in this element: %s" ); + set( ERROR_FL_NOTE_PAT_IDX_TOO_HIGH, "pattern index '%s' too high in this element: %s" ); set( ERROR_FUNC_TYPE_NOT_BOOL, "Effect type not boolean. Function '%s' is not allowed." ); set( ERROR_FUNC_TYPE_BOOL, "Value type is 'boolean'. Function '%s' is not allowed." ); set( ERROR_FUNC_TYPE_NONE, "Value type is 'none'. Function '%s' is not allowed." ); @@ -4382,7 +4392,7 @@ public static void initSyntax() { setSyntax( SYNTAX_CH_A_POLY_AT, "poly_at" ); setSyntax( SYNTAX_CH_D_MONO_AT, "mono_at" ); setSyntax( SYNTAX_CH_E_PITCH_BEND, "bend" ); - setSyntax( SYNTAX_CC_01_MOD, "modulation" ); + setSyntax( SYNTAX_CC_01_MOD, "mod" ); setSyntax( SYNTAX_CC_02_BREATH, "breath" ); setSyntax( SYNTAX_CC_04_FOOT, "foot" ); setSyntax( SYNTAX_CC_05_PORT_TIME, "port_time" ); @@ -4412,6 +4422,8 @@ public static void initSyntax() { setSyntax( SYNTAX_CC_5D_EFF3_DEP, "chorus" ); setSyntax( SYNTAX_CC_5E_EFF4_DEP, "detune" ); setSyntax( SYNTAX_CC_5F_EFF4_DEP, "phaser" ); + setSyntax( SYNTAX_CC_7E_MONO_MODE, "mono_mode" ); + setSyntax( SYNTAX_CC_7F_POLY_MODE, "poly_mode" ); setSyntax( SYNTAX_RPN_0_PITCH_BEND_R, "pitch_bend_range" ); setSyntax( SYNTAX_RPN_1_FINE_TUNE, "fine_tune" ); setSyntax( SYNTAX_RPN_2_COARSE_TUNE, "coarse_tune" ); @@ -4626,6 +4638,8 @@ else if (Config.CBX_SYNTAX_UPPER.equals(configuredSyntax)) { addSyntaxForInfoView( SYNTAX_CC_5D_EFF3_DEP ); addSyntaxForInfoView( SYNTAX_CC_5E_EFF4_DEP ); addSyntaxForInfoView( SYNTAX_CC_5F_EFF4_DEP ); + addSyntaxForInfoView( SYNTAX_CC_7E_MONO_MODE ); + addSyntaxForInfoView( SYNTAX_CC_7F_POLY_MODE ); addSyntaxForInfoView( SYNTAX_RPN_0_PITCH_BEND_R ); addSyntaxForInfoView( SYNTAX_RPN_1_FINE_TUNE ); addSyntaxForInfoView( SYNTAX_RPN_2_COARSE_TUNE ); diff --git a/src/org/midica/file/read/Effect.java b/src/org/midica/file/read/Effect.java index aa3610c..de15c73 100644 --- a/src/org/midica/file/read/Effect.java +++ b/src/org/midica/file/read/Effect.java @@ -50,6 +50,7 @@ public class Effect { private static Pattern flowPattern = null; private static Pattern noteOrRestPattern = null; + private static Pattern noteIndexPattern = null; private static Pattern intPattern = null; private static Pattern periodsPattern = null; @@ -132,6 +133,8 @@ public static void init(MidicaPLParser rootParser) { effectNames.add( MidicaPLParser.CC_5D_EFF3_DEP ); effectNames.add( MidicaPLParser.CC_5E_EFF4_DEP ); effectNames.add( MidicaPLParser.CC_5F_EFF4_DEP ); + effectNames.add( MidicaPLParser.CC_7E_MONO_MODE ); + effectNames.add( MidicaPLParser.CC_7F_POLY_MODE ); effectNames.add( MidicaPLParser.RPN_0_PITCH_BEND_R ); effectNames.add( MidicaPLParser.RPN_1_FINE_TUNE ); effectNames.add( MidicaPLParser.RPN_2_COARSE_TUNE ); @@ -178,6 +181,8 @@ public static void init(MidicaPLParser rootParser) { ctrlNameToNumber.put( MidicaPLParser.CC_5D_EFF3_DEP, 0x5D ); ctrlNameToNumber.put( MidicaPLParser.CC_5E_EFF4_DEP, 0x5E ); ctrlNameToNumber.put( MidicaPLParser.CC_5F_EFF4_DEP, 0x5F ); + ctrlNameToNumber.put( MidicaPLParser.CC_7E_MONO_MODE, 0x7E ); + ctrlNameToNumber.put( MidicaPLParser.CC_7F_POLY_MODE, 0x7F ); // init structures for RPN-based effects rpnNameToNumber.put( MidicaPLParser.RPN_0_PITCH_BEND_R, 0x0000 ); @@ -197,49 +202,69 @@ public static void init(MidicaPLParser rootParser) { flowElementNames.add(MidicaPLParser.FL_DOUBLE); // compile regex patterns - String flowRegex - = "\\G" // end of previous match - + "(^|" + Pattern.quote(MidicaPLParser.FL_DOT) + ")?" // begin or '.' - + "(\\w+)" // name - + "(?:" // generic number (optional) - + Pattern.quote(MidicaPLParser.FL_ASSIGNER) - + "(\\d+)" // MSB or whole number - + "(?:" - + Pattern.quote(MidicaPLParser.FL_GEN_NUM_SEP) - + "(\\d+)" // LSB of generic number - + ")?" - + ")?" - + "(?:" // parameters (optional) - + Pattern.quote(MidicaPLParser.PARAM_OPEN) // ( - + "(\\S*?)" // function parameters - + Pattern.quote(MidicaPLParser.PARAM_CLOSE) // ) - + ")?"; // optional - flowPattern = Pattern.compile(flowRegex); - noteOrRestPattern = Pattern.compile("^[0-9]|" + Pattern.quote(MidicaPLParser.REST) + "$"); - String intRegex = "^" - + "(\\-?\\d+)" // direct int value (group 1) - + "|" - + "(?:" - + "(\\-?\\d+(?:\\.\\d+)?)" // percentage value (group 2) - + Pattern.quote(MidicaPLParser.EFF_PERCENT) - + ")" - + "|" - + "(\\-?\\d+\\.\\d+)" // half-tone-steps (group 3) - + "|" - + "(?:" - + "(\\d+)" // MSB (group 4) - + Pattern.quote(MidicaPLParser.FL_GEN_NUM_SEP) - + "(\\d+)" // LSB (group 5) - + ")" - + "$"; - intPattern = Pattern.compile(intRegex); - String periodsRegex = "^" - + "(\\-?\\d+(?:\\.\\d*))" // int or float (group 1) - + "(" // percent (group 2) - + Pattern.quote(MidicaPLParser.EFF_PERCENT) - + ")?" - + "$"; - periodsPattern = Pattern.compile(periodsRegex); + { + // flow pattern + String flowRegex + = "\\G" // end of previous match + + "(^|" + Pattern.quote(MidicaPLParser.FL_DOT) + ")?" // begin or '.' + + "(\\w+)" // name + + "(?:" // generic number (optional) + + Pattern.quote(MidicaPLParser.FL_ASSIGNER) + + "(\\d+)" // MSB or whole number + + "(?:" + + Pattern.quote(MidicaPLParser.FL_GEN_NUM_SEP) + + "(\\d+)" // LSB of generic number + + ")?" + + ")?" + + "(?:" // parameters (optional) + + Pattern.quote(MidicaPLParser.PARAM_OPEN) // ( + + "(\\S*?)" // function parameters + + Pattern.quote(MidicaPLParser.PARAM_CLOSE) // ) + + ")?"; // optional + flowPattern = Pattern.compile(flowRegex); + + // note index pattern (to replace .note(idx) by .note(name)) + String noteIndexRegex + = "\\G" // end of previous match + + "(.*?)" // anything (group 1) + + Pattern.quote(MidicaPLParser.FL_DOT) // '.' + + Pattern.quote(MidicaPLParser.FUNC_NOTE) // note + + Pattern.quote(MidicaPLParser.PARAM_OPEN) // ( + + "(\\S*?)" // parameter (group 2) + + Pattern.quote(MidicaPLParser.PARAM_CLOSE); // ) + noteIndexPattern = Pattern.compile(noteIndexRegex); + + // note/rest pattern + noteOrRestPattern = Pattern.compile("^[0-9]|" + Pattern.quote(MidicaPLParser.REST) + "$"); + + // int pattern (for function parameters) + String intRegex = "^" + + "(\\-?\\d+)" // direct int value (group 1) + + "|" + + "(?:" + + "(\\-?\\d+(?:\\.\\d+)?)" // percentage value (group 2) + + Pattern.quote(MidicaPLParser.EFF_PERCENT) + + ")" + + "|" + + "(\\-?\\d+\\.\\d+)" // half-tone-steps (group 3) + + "|" + + "(?:" + + "(\\d+)" // MSB (group 4) + + Pattern.quote(MidicaPLParser.FL_GEN_NUM_SEP) + + "(\\d+)" // LSB (group 5) + + ")" + + "$"; + intPattern = Pattern.compile(intRegex); + + // periods pattern (for function parameters) + String periodsRegex = "^" + + "(\\-?\\d+(?:\\.\\d*))" // int or float (group 1) + + "(" // percent (group 2) + + Pattern.quote(MidicaPLParser.EFF_PERCENT) + + ")?" + + "$"; + periodsPattern = Pattern.compile(periodsRegex); + } } /** @@ -308,10 +333,6 @@ public static boolean applyFlowIfPossible(int channel, String flowStr, String le String numberLsb = m.group(4); String paramStr = m.group(5); - // flow or something else? - if (null == elemName) { - return false; - } if (flowElementNames.contains(elemName)) looksLikeFlow = true; else @@ -376,6 +397,57 @@ public static void closeFlowIfPossible() { flow = null; } + /** + * Translates the pattern indices inside the given effect flow to notes. + * + * Needed for pattern calls. + * + * @param flowStr the effect flow to be translated + * @param noteNumbers the note numbers + * @return the translated flow + * @throws ParseException if an index is not a positive integer or too high for the given noteNumbers. + */ + public static String translatePatternIndices(String flowStr, Integer[] noteNumbers) throws ParseException { + + String translated = ""; + Matcher m = noteIndexPattern.matcher(flowStr); + int lastMatchOffset = 0; + while (m.find()) { + lastMatchOffset = m.end(); + String beforeStr = m.group(1); + String indexStr = m.group(2); + try { + int index = Integer.parseInt(indexStr); + int note = noteNumbers[index]; + translated += beforeStr + MidicaPLParser.FL_DOT + MidicaPLParser.FUNC_NOTE + + MidicaPLParser.PARAM_OPEN + note + MidicaPLParser.PARAM_CLOSE; + + } + catch (NumberFormatException e) { + String noteElem = MidicaPLParser.FL_DOT + MidicaPLParser.FUNC_NOTE + + MidicaPLParser.PARAM_OPEN + indexStr + MidicaPLParser.PARAM_CLOSE; + throw new ParseException( + String.format(Dict.get(Dict.ERROR_FL_NOTE_PAT_IDX_NAN), indexStr, noteElem) + ); + } + catch (IndexOutOfBoundsException e) { + String noteElem = MidicaPLParser.FL_DOT + MidicaPLParser.FUNC_NOTE + + MidicaPLParser.PARAM_OPEN + indexStr + MidicaPLParser.PARAM_CLOSE; + throw new ParseException( + String.format(Dict.get(Dict.ERROR_FL_NOTE_PAT_IDX_TOO_HIGH), indexStr, noteElem) + ); + } + } + + // unmatched characters left? + if (lastMatchOffset != flowStr.length()) { + String remainder = flowStr.substring(lastMatchOffset); + translated += remainder; + } + + return translated; + } + /** * Parses a generic controller or (N)RPN number, assigned in a flow. * @@ -569,14 +641,24 @@ private static void applyFunction(String funcName, String[] params) throws Parse // for all other functions we need the effect type int valueType = flow.getValueType(funcName); + // note required but not set? + if (flow.needsNote() && flow.getNote() < 0) + throw new ParseException(Dict.get(Dict.ERROR_FL_NOTE_NOT_SET) + funcName); + // on()/off() - boolean functions if (MidicaPLParser.FUNC_ON.equals(funcName) || MidicaPLParser.FUNC_OFF.equals(funcName)) { int value = MidicaPLParser.FUNC_ON.equals(funcName) ? 127 : 0; // check type - if (valueType == EffectFlow.TYPE_NONE && MidicaPLParser.FUNC_ON.equals(funcName)) { - // special case: allow on() for type NONE (but then use value=0) - value = 0; + if (MidicaPLParser.FUNC_ON.equals(funcName)) { + + // special case: allow on() for ANY / NONE (but then use a predefined value) + if (valueType == EffectFlow.TYPE_NONE || valueType == EffectFlow.TYPE_ANY) { + value = flow.getDefaultValueForOn(valueType); + } + else if (valueType != EffectFlow.TYPE_BOOLEAN) { + throw new ParseException(String.format(Dict.get(Dict.ERROR_FUNC_TYPE_NOT_BOOL), funcName)); + } } else if (valueType != EffectFlow.TYPE_BOOLEAN && valueType != EffectFlow.TYPE_ANY) { throw new ParseException(String.format(Dict.get(Dict.ERROR_FUNC_TYPE_NOT_BOOL), funcName)); @@ -649,20 +731,28 @@ private static void setValue(int[] values) throws ParseException { if (2 == values.length) { // pitch bend? - if (0xE0 == flow.getEffectNumber()) { + if (0xE0 == effectNum) { // special case: set(0) for MSB only - set the LSB to 0 - to get no bend at all if (0x40 == values[1]) SequenceCreator.addMessageChannelEffect(effectNum, channel, 0, values[1], tick); else // use the MSB in both positions - cover the whole range SequenceCreator.addMessageChannelEffect(effectNum, channel, values[1], values[1], tick); + return; } - else { - // TODO: mono_at, poly_at - // MSB first, LSB second + + // mono_at? + if (0xD0 == effectNum) { SequenceCreator.addMessageChannelEffect(effectNum, channel, values[1], 0, tick); + return; + } + + // poly_at? + if (0xA0 == effectNum) { + int note = flow.getNote(); + SequenceCreator.addMessageChannelEffect(effectNum, channel, note, values[1], tick); + return; } - return; } else if (3 == values.length) { // pitch bend: lsb first, msb second diff --git a/src/org/midica/file/read/EffectFlow.java b/src/org/midica/file/read/EffectFlow.java index f954457..1e49c1c 100644 --- a/src/org/midica/file/read/EffectFlow.java +++ b/src/org/midica/file/read/EffectFlow.java @@ -179,6 +179,65 @@ public boolean mustUseHalfToneSteps() { return false; } + /** + * Determines if the current effect needs a note to be set. + * + * @return **true** if a note is needed, otherwise **false**. + */ + public boolean needsNote() { + + // poly_at? + if (EFF_TYPE_CHANNEL == effectType && 0xA0 == effectNumber) + return true; + + // port_ctrl? + if (EFF_TYPE_CTRL == effectType && 0x54 == effectNumber) + return true; + + return false; + } + + /** + * Returns the pre-defined value for the current effect (with type=NONE or type=ANY). + * + * For most effects with type NONE the predefined value is 0. + * + * For most effects with type ANY the predefined value is 127. + * + * Exceptions are: + * + * - **port_ctrl** - Here the default is the configured note() + * - **mono_mode** - Here the default is 1 + * + * @param valueType the effect's value type + * @return the default value to be set for **on()**. + * @throws FatalParseException + */ + public int getDefaultValueForOn(int valueType) throws FatalParseException { + + // NONE + if (TYPE_NONE == valueType) { + + // port_ctrl? + if (EFF_TYPE_CTRL == effectType && 0x54 == effectNumber) + return note; + + return 0; + } + + // ANY + if (TYPE_ANY == valueType) { + + // mono_mode + if (EFF_TYPE_CTRL == effectType && 0x7E == effectNumber) + return 1; + + return 127; + } + + throw new FatalParseException("Invalid value type '" + valueType + "' for getDefaultValueForOn()."); + } + /** * Applies a **length(...)** function call in the flow. * @@ -216,6 +275,17 @@ else if (EFF_TYPE_CTRL == effectType && 0x54 == effectNumber) // portamento ctrl this.note = note; } + /** + * Returns the MIDI note number of the currently set note(). + * + * Returns **-1** if no note has been set. + * + * @return note number or **-1** if no note has been set. + */ + public int getNote() { + return note; + } + /** * Apppies a **double** flow element. * @@ -498,18 +568,21 @@ else if (EFF_TYPE_NRPN == effectType) { } // exceptions for the above ranges - ctrlToType.put(0x08, TYPE_MSB_SIGNED); // portamento ctrl - ctrlToType.put(0x0A, TYPE_MSB_SIGNED); // portamento ctrl - ctrlToType.put(0x54, TYPE_ANY); // portamento ctrl + ctrlToType.put(0x08, TYPE_MSB_SIGNED); // balance + ctrlToType.put(0x0A, TYPE_MSB_SIGNED); // panorama + ctrlToType.put(0x54, TYPE_NONE); // portamento ctrl ctrlToType.put(0x58, TYPE_ANY); // high resolution velocity prefix ctrlToType.put(0x60, TYPE_NONE); // data increment ctrlToType.put(0x61, TYPE_NONE); // data decrement ctrlToType.put(0x7A, TYPE_BOOLEAN); // local control on/off - ctrlToType.put(0x7E, TYPE_BYTE); // mono mode on + ctrlToType.put(0x7E, TYPE_ANY); // mono mode on // min / max applyDefaultMinAndMax(ctrlToType, ctrlToDefault, ctrlToMin, ctrlToMax); + // exceptions for max + ctrlToMax.put(0x7E, 16); // mono mode on - range: 0-16 + ///////////////////////////// // (N)RPNs ///////////////////////////// diff --git a/src/org/midica/file/read/MidicaPLParser.java b/src/org/midica/file/read/MidicaPLParser.java index 9b386e3..f9361ce 100644 --- a/src/org/midica/file/read/MidicaPLParser.java +++ b/src/org/midica/file/read/MidicaPLParser.java @@ -277,6 +277,8 @@ public class MidicaPLParser extends SequenceParser { public static String CC_5D_EFF3_DEP = null; public static String CC_5E_EFF4_DEP = null; public static String CC_5F_EFF4_DEP = null; + public static String CC_7E_MONO_MODE = null; + public static String CC_7F_POLY_MODE = null; public static String RPN_0_PITCH_BEND_R = null; public static String RPN_1_FINE_TUNE = null; public static String RPN_2_COARSE_TUNE = null; @@ -560,6 +562,8 @@ public static void refreshSyntax() { CC_5D_EFF3_DEP = Dict.getSyntax( Dict.SYNTAX_CC_5D_EFF3_DEP ); CC_5E_EFF4_DEP = Dict.getSyntax( Dict.SYNTAX_CC_5E_EFF4_DEP ); CC_5F_EFF4_DEP = Dict.getSyntax( Dict.SYNTAX_CC_5F_EFF4_DEP ); + CC_7E_MONO_MODE = Dict.getSyntax( Dict.SYNTAX_CC_7E_MONO_MODE ); + CC_7F_POLY_MODE = Dict.getSyntax( Dict.SYNTAX_CC_7F_POLY_MODE ); RPN_0_PITCH_BEND_R = Dict.getSyntax( Dict.SYNTAX_RPN_0_PITCH_BEND_R ); RPN_1_FINE_TUNE = Dict.getSyntax( Dict.SYNTAX_RPN_1_FINE_TUNE ); RPN_2_COARSE_TUNE = Dict.getSyntax( Dict.SYNTAX_RPN_2_COARSE_TUNE ); @@ -2806,13 +2810,13 @@ else if (OPT_SHIFT.equals(optName)) { if (compactElement.startsWith(BAR_LINE)) continue; - // rest/note/chord + // rest/note/chord/flow String[] parts = compactElement.split(Pattern.quote(COMPACT_NOTE_SEP), 2); String chord = parts[0]; if (REST.equals(chord)) continue; - patLineTokens[j] = patternIndicesToChord(chord, noteNumbers); + patLineTokens[j] = translatePatternIndices(chord, noteNumbers); if (parts.length > 1) patLineTokens[j] += COMPACT_NOTE_SEP + parts[1]; } @@ -2867,8 +2871,8 @@ else if (OPT_TREMOLO.equals(optName)) { lineNotes.add(REST); } else { - // note or chord - String noteOrChord = patternIndicesToChord(patLineTokens[0], noteNumbers); + // note or chord or effect flow + String noteOrChord = translatePatternIndices(patLineTokens[0], noteNumbers); lineNotes.add(noteOrChord); } @@ -2934,20 +2938,30 @@ else if (OPT_TREMOLO.equals(optName)) { } /** - * Translates a string with comma-separated indices to a chord with comma-separated notes. + * Translates the pattern indices inside the given string to notes. + * + * The given string can be one of the following: + * + * - slash-separated indices + * - an effect flow * * Needed for pattern calls. * - * @param indicesStr the comma-separated indices string (e.g. "0,2,3") - * @param noteNumbers the note numbers to be used for the chord (e.g. [64, 66, 67, 76]) - * @return the chord (e.g. "64,67,76") - * @throws ParseException if an index is not a positie integer or too high for the given noteNumbers. + * @param strWithIndices the slash-separated indices string (e.g. "0/2/3") or the effect flow (e.g. ".note(0)") + * @param noteNumbers the note numbers to be used for the replacement (e.g. [64, 66, 67, 76]) + * @return the chord (e.g. "64/67/76") or effect flow (e.g. ".note(64)") + * @throws ParseException if an index is not a positive integer or too high for the given noteNumbers. */ - private String patternIndicesToChord(String indicesStr, Integer[] noteNumbers) throws ParseException { + private String translatePatternIndices(String strWithIndices, Integer[] noteNumbers) throws ParseException { ArrayList notes = new ArrayList<>(); + // effect flow? - replace index in .note(...) + if (Effect.isFlow(strWithIndices)) { + return Effect.translatePatternIndices(strWithIndices, noteNumbers); + } + // note or chord - String[] indexStrings = indicesStr.split(Pattern.quote(CHORD_SEPARATOR), -1); + String[] indexStrings = strWithIndices.split(Pattern.quote(CHORD_SEPARATOR), -1); for (String indexStr : indexStrings) { try { int index = Integer.parseInt(indexStr); @@ -3080,7 +3094,7 @@ else if (COMPACT_CHANNEL.equals(tokens[0])) { // ok - checks will be done later when the pattern is called } - // normal channel command + // lowlevel channel command else { if (tokens.length < 2) { throw new ParseException(Dict.get(Dict.ERROR_PATTERN_NUM_OF_ARGS)); @@ -3092,7 +3106,7 @@ else if (COMPACT_CHANNEL.equals(tokens[0])) { // They will be checked later, when the pattern is called } else { - // check if indices are numbers + // check if indices are numbers or effect flows checkPatternIndices(tokens[0]); } @@ -3122,14 +3136,20 @@ else if (0 == tokens.length) { } /** - * Checks if the given string consists of comma-separated note index numbers. + * Checks if the given string consists of slash-separated note index numbers or an effect flow. * * Needed to check a pattern at pattern line parsing time (before any pattern call). * - * @param indicesStr - * @throws ParseException + * @param indicesStr string to be checked + * @throws ParseException if the check fails */ private void checkPatternIndices(String indicesStr) throws ParseException { + + // effect flow? + if (Effect.isFlow(indicesStr)) + return; + + // note indices String[] indexStrings = indicesStr.split(Pattern.quote(CHORD_SEPARATOR), -1); for (String indexStr : indexStrings) { try { @@ -3805,6 +3825,8 @@ else if (3 == tokens.length) { else if ( Dict.SYNTAX_CC_5D_EFF3_DEP.equals(cmdId) ) CC_5D_EFF3_DEP = cmdName; else if ( Dict.SYNTAX_CC_5E_EFF4_DEP.equals(cmdId) ) CC_5E_EFF4_DEP = cmdName; else if ( Dict.SYNTAX_CC_5F_EFF4_DEP.equals(cmdId) ) CC_5F_EFF4_DEP = cmdName; + else if ( Dict.SYNTAX_CC_7E_MONO_MODE.equals(cmdId) ) CC_7E_MONO_MODE = cmdName; + else if ( Dict.SYNTAX_CC_7F_POLY_MODE.equals(cmdId) ) CC_7F_POLY_MODE = cmdName; else if ( Dict.SYNTAX_RPN_0_PITCH_BEND_R.equals(cmdId) ) RPN_0_PITCH_BEND_R = cmdName; else if ( Dict.SYNTAX_RPN_1_FINE_TUNE.equals(cmdId) ) RPN_1_FINE_TUNE = cmdName; else if ( Dict.SYNTAX_RPN_2_COARSE_TUNE.equals(cmdId) ) RPN_2_COARSE_TUNE = cmdName; @@ -4804,13 +4826,15 @@ private void parseChannelCmd(String[] tokens, boolean isFake) throws ParseExcept String durationStr = subTokens[0]; // effect? - if (!isFake) { - if (Effect.applyFlowIfPossible(channel, tokens[1], durationStr)) { + if (isFake) { + if (Effect.isFlow(tokens[1])) return; - } - else { + } + else { + if (Effect.applyFlowIfPossible(channel, tokens[1], durationStr)) + return; + else Effect.closeFlowIfPossible(); - } } // note or rest @@ -5320,7 +5344,7 @@ private ArrayList parseChord(String token) throws ParseException { if (token.matches(".*" + Pattern.quote(CHORD_SEPARATOR) + ".*") || chords.containsKey(token)) { ArrayList chordElements = new ArrayList<>(); - // collect comma-separated inline chord parts + // collect slash-separated inline chord parts String[] inlineElements = token.split(Pattern.quote(CHORD_SEPARATOR)); for (String inlineElement : inlineElements) { diff --git a/src/org/midica/ui/model/SingleMessage.java b/src/org/midica/ui/model/SingleMessage.java index 4ae0789..fa9b9ef 100644 --- a/src/org/midica/ui/model/SingleMessage.java +++ b/src/org/midica/ui/model/SingleMessage.java @@ -80,12 +80,23 @@ public String getRange(int id) { */ @Override public String getDistinctOptions(int id) { - if ( ! options.containsKey(id) ) + if (! options.containsKey(id)) return null; return options.get(id) + ""; } + /** + * Returns the message as a byte array. + * + * Needed for unit tests. + * + * @return message bytes + */ + public byte[] getMessageBytes() { + return (byte[]) getOption(IMessageType.OPT_MESSAGE); + } + /** * Returns the custom option with the given ID. * @@ -260,7 +271,8 @@ public String toString() { + "/" + (Integer) getOption(IMessageType.OPT_CHANNEL) + "/" + (String) getOption(IMessageType.OPT_STATUS_BYTE) + "-" + ctrlByte - + "/" + (String) getOption(IMessageType.OPT_SUMMARY); + + "/" + (String) getOption(IMessageType.OPT_SUMMARY) + ; } // something else diff --git a/test/org/midica/file/read/MidicaPLParserTest.java b/test/org/midica/file/read/MidicaPLParserTest.java index c96013a..cd27059 100644 --- a/test/org/midica/file/read/MidicaPLParserTest.java +++ b/test/org/midica/file/read/MidicaPLParserTest.java @@ -1471,7 +1471,7 @@ void testParseFilesWorking() throws ParseException { parse(getWorkingFile("bar-lines")); - parse(getWorkingFile("effects-1")); + parse(getWorkingFile("effects-1-set")); // channel 0 { messages = getMessagesByStatus("B0"); @@ -1857,6 +1857,579 @@ void testParseFilesWorking() throws ParseException { } } + parse(getWorkingFile("effects-2-bend")); + // channel 0: pitch bend range + { + messages = getMessagesByStatus("B0"); + int i = 0; + + // tick 60: pitch_bend_range.length(32).wait.set(0.0) + assertEquals( "30/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "40/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "50/0/B0-06/0", messages.get(i++).toString() ); // data MSB: 0 + assertEquals( "70/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "70/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 120: .wait.set(127.0) + assertEquals( "90/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "100/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "110/0/B0-06/127", messages.get(i++).toString() ); // data MSB: 127 + assertEquals( "130/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "130/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 180: .wait.set(12.7) + assertEquals( "150/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "160/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "170/0/B0-06/13", messages.get(i++).toString() ); // data MSB: 13 + assertEquals( "190/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "190/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 240: .wait.set(12.0) + assertEquals( "210/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "220/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "230/0/B0-06/12", messages.get(i++).toString() ); // data MSB: 12 + assertEquals( "250/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "250/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 300: .wait.set(127) + assertEquals( "270/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "280/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "290/0/B0-06/127", messages.get(i++).toString() ); // data MSB: 127 + assertEquals( "310/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "310/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 360: .double.wait.set(0.0) + assertEquals( "330/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "340/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "350/0/B0-06/0", messages.get(i++).toString() ); // data MSB: 0 + assertEquals( "360/0/B0-26/0", messages.get(i++).toString() ); // data LSB: 0 + assertEquals( "370/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "370/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 420: .wait.set(127/5) + assertEquals( "390/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "400/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "410/0/B0-06/127", messages.get(i++).toString() ); // data MSB: 127 + assertEquals( "420/0/B0-26/5", messages.get(i++).toString() ); // data LSB: 5 + assertEquals( "430/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "430/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 480: .wait.set(124.998) + assertEquals( "450/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "460/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "470/0/B0-06/125", messages.get(i++).toString() ); // data MSB: 125 + assertEquals( "480/0/B0-26/0", messages.get(i++).toString() ); // data LSB: 0 + assertEquals( "490/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "490/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 540: .wait.set(4.997) + assertEquals( "510/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "520/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "530/0/B0-06/5", messages.get(i++).toString() ); // data MSB: 5 + assertEquals( "540/0/B0-26/0", messages.get(i++).toString() ); // data LSB: 0 + assertEquals( "550/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "550/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 600: .wait.set(127.990) + assertEquals( "570/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "580/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "590/0/B0-06/127", messages.get(i++).toString() ); // data MSB: 127 + assertEquals( "600/0/B0-26/99", messages.get(i++).toString() ); // data LSB: 99 + assertEquals( "610/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "610/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 660: .wait.set(-0.000001) + assertEquals( "630/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "640/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "650/0/B0-06/0", messages.get(i++).toString() ); // data MSB: 0 + assertEquals( "660/0/B0-26/0", messages.get(i++).toString() ); // data LSB: 0 + assertEquals( "670/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "670/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 720: .wait.set(127) + assertEquals( "690/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "700/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "710/0/B0-06/127", messages.get(i++).toString() ); // data MSB: 127 + assertEquals( "720/0/B0-26/0", messages.get(i++).toString() ); // data LSB: 0 + assertEquals( "730/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "730/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 960: rpn=0/0.set(96.7) + assertEquals( "930/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "940/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "950/0/B0-06/97", messages.get(i++).toString() ); // data MSB: 97 + assertEquals( "970/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "970/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 1440: rpn=0/0.double.set(96.7) + assertEquals( "1410/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "1420/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "1430/0/B0-06/96", messages.get(i++).toString() ); // data MSB: 96 + assertEquals( "1440/0/B0-26/70", messages.get(i++).toString() ); // data LSB: 70 + assertEquals( "1450/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "1450/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 1920: rpn=0.set(2.0) + assertEquals( "1890/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "1900/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "1910/0/B0-06/2", messages.get(i++).toString() ); // data MSB: 2 + assertEquals( "1930/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "1930/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + + // tick 2400: rpn=0.double.set(2.0) + assertEquals( "2370/0/B0-65/0", messages.get(i++).toString() ); // RPN MSB: 0 + assertEquals( "2380/0/B0-64/0", messages.get(i++).toString() ); // RPN LSB: 0 + assertEquals( "2390/0/B0-06/2", messages.get(i++).toString() ); // data MSB: 2 + assertEquals( "2400/0/B0-26/0", messages.get(i++).toString() ); // data LSB: 0 + assertEquals( "2410/0/B0-65/127", messages.get(i++).toString() ); // MSB reset + assertEquals( "2410/0/B0-64/127", messages.get(i++).toString() ); // LSB reset + } + // channel 1 + { + messages = getMessagesByStatus("E1"); + int i = 0; + + // percentage, range: 2.0 + { + // single byte + assertEquals("00/00/-2.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(-100%) + assertEquals("20/20/-1.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(-50%) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0%) + assertEquals("60/60/1.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(50%) + assertEquals("7F/7F/2.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(100%) + + // double + assertEquals("00/00/-2.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-100%) + assertEquals("20/00/-1.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-50%) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0%) + assertEquals("60/00/1.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(50%) + assertEquals("7F/7F/2.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(100%) + } + + // MSB/LSB, range: 2.0 + { + assertEquals("00/00/-2.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0/0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(64/0) + assertEquals("7F/7F/2.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(127/127) + } + + // half-tone, range: 2.7 + { + // single + assertEquals("15/15/-2.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(-2.0) + assertEquals("2B/2B/-1.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(-1.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("55/55/1.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(1.0) + assertEquals("6A/6A/2.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(2.0) + + // double + assertEquals("15/2B/-2.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(-2.0) + assertEquals("2A/55/-1.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(-1) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("55/2A/1.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(1.0) + assertEquals("6A/55/2.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(2) + } + + // half-tone, range: 4.0 + { + // single + assertEquals("00/00/-4.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(-4.0) + assertEquals("20/20/-2.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(-2) + assertEquals("30/30/-1.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(-1.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("50/50/1.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(1.0) + assertEquals("60/60/2.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(2.0) + assertEquals("7F/7F/4.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(4) + + // double + assertEquals("00/00/-4.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-4.0) + assertEquals("20/00/-2.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-2.0) + assertEquals("30/00/-1.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-1.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("50/00/1.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(1.0) + assertEquals("60/00/2.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(2.0) + assertEquals("7F/7F/4.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(4.0) + } + // half-tone, range: 8.0 + { + // single + assertEquals("00/00/-8.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(-8.0) + assertEquals("30/30/-2.0", getPitchBendStr(messages.get(i++), 1)); // bend.set(-2.0) + assertEquals("38/38/-1" , getPitchBendStr(messages.get(i++), 0)); // bend.set(-1.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("48/48/1", getPitchBendStr(messages.get(i++), 0)); // bend.set(1.0) + assertEquals("50/50/2", getPitchBendStr(messages.get(i++), 0)); // bend.set(2.0) + assertEquals("7F/7F/8.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(8.0) + + // double + assertEquals("00/00/-8.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-8.0) + assertEquals("30/00/-2.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-2.0) + assertEquals("38/00/-1.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-1.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("48/00/1.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(1.0) + assertEquals("50/00/2.000", getPitchBendStr(messages.get(i++), 3)); // bend.double.set(2.0) + assertEquals("7F/7F/8.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(8.0) + } + // half-tone, range: 12.0 + { + // single + assertEquals("00/00/-12.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("7F/7F/12.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(12.0) + + // double + assertEquals("00/00/-12.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("7F/7F/12.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(12.0) + } + // half-tone, range: 24.0 + { + // single + assertEquals("00/00/-24.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(-24.0) + assertEquals("20/20/-12", getPitchBendStr(messages.get(i++), 0)); // bend.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("60/60/12", getPitchBendStr(messages.get(i++), 0)); // bend.set(12.0) + assertEquals("7F/7F/24.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(24.0) + + // double + assertEquals("00/00/-24.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-24.0) + assertEquals("20/00/-12.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("60/00/12.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(12.0) + assertEquals("7F/7F/24.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(24.0) + } + // half-tone, range: 36.0 + { + // single + assertEquals("00/00/-36.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(-36.0) + assertEquals("15/15/-24", getPitchBendStr(messages.get(i++), 0)); // bend.set(-24.0) + assertEquals("2B/2B/-12", getPitchBendStr(messages.get(i++), 0)); // bend.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("55/55/12", getPitchBendStr(messages.get(i++), 0)); // bend.set(12.0) + assertEquals("6A/6A/24", getPitchBendStr(messages.get(i++), 0)); // bend.set(24.0) + assertEquals("7F/7F/36.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(36.0) + + // double + assertEquals("00/00/-36.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-36.0) + assertEquals("15/2B/-24.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(-24.0) + assertEquals("2A/55/-12.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("55/2A/12.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(12.0) + assertEquals("6A/55/24.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(24.0) + assertEquals("7F/7F/36.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(36.0) + } + // half-tone, range: 48.0 + { + // single + assertEquals("00/00/-48.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(-48.0) + assertEquals("10/10/-36", getPitchBendStr(messages.get(i++), 0)); // bend.set(-36.0) + assertEquals("20/20/-24", getPitchBendStr(messages.get(i++), 0)); // bend.set(-24.0) + assertEquals("30/30/-12", getPitchBendStr(messages.get(i++), 0)); // bend.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("50/50/12", getPitchBendStr(messages.get(i++), 0)); // bend.set(12.0) + assertEquals("60/60/25", getPitchBendStr(messages.get(i++), 0)); // bend.set(24.0) - rounding error too big + assertEquals("6F/6F/36", getPitchBendStr(messages.get(i++), 0)); // bend.set(36.0) + assertEquals("7F/7F/48.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(48.0) + + // double + assertEquals("00/00/-48.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-48.0) + assertEquals("10/00/-36.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-36.0) + assertEquals("20/00/-24.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-24.0) + assertEquals("30/00/-12.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("50/00/12.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(12.0) + assertEquals("60/00/24.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(24.0) + assertEquals("6F/7F/36.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(36.0) + assertEquals("7F/7F/48.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(48.0) + } + // half-tone, range: 60.0 + { + // single + assertEquals("00/00/-60.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(-60.0) + assertEquals("0D/0D/-48", getPitchBendStr(messages.get(i++), 0)); // bend.set(-48.0) + assertEquals("1A/1A/-35", getPitchBendStr(messages.get(i++), 0)); // bend.set(-36.0) - rounding error too big + assertEquals("26/26/-24", getPitchBendStr(messages.get(i++), 0)); // bend.set(-24.0) + assertEquals("33/33/-12", getPitchBendStr(messages.get(i++), 0)); // bend.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("4D/4D/13", getPitchBendStr(messages.get(i++), 0)); // bend.set(12.0) - rounding error too big + assertEquals("59/59/24", getPitchBendStr(messages.get(i++), 0)); // bend.set(24.0) + assertEquals("66/66/36", getPitchBendStr(messages.get(i++), 0)); // bend.set(36.0) + assertEquals("72/72/48", getPitchBendStr(messages.get(i++), 0)); // bend.set(48.0) + assertEquals("7F/7F/60.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(60.0) + + // double + assertEquals("00/00/-60.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(-60.0) + assertEquals("0C/66/-48.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(-48.0) + assertEquals("19/4D/-36.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(-36.0) + assertEquals("26/33/-24.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(-24.0) + assertEquals("33/1A/-12.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("4C/66/12.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(12.0) + assertEquals("59/4C/24.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(24.0) + assertEquals("66/33/36.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(36.0) + assertEquals("73/19/48.00", getPitchBendStr(messages.get(i++), 2)); // bend.double.set(48.0) + assertEquals("7F/7F/60.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(60.0) + } + // half-tone, range: 127.99 + { + // single + assertEquals("22/22/-59", getPitchBendStr(messages.get(i++), 0)); // bend.set(-60.0) - rounding error too big + assertEquals("28/28/-47", getPitchBendStr(messages.get(i++), 0)); // bend.set(-48.0) - rounding error too big + assertEquals("2E/2E/-35", getPitchBendStr(messages.get(i++), 0)); // bend.set(-36.0) - rounding error too big + assertEquals("34/34/-23", getPitchBendStr(messages.get(i++), 0)); // bend.set(-24.0) - rounding error too big + assertEquals("3A/3A/-11", getPitchBendStr(messages.get(i++), 0)); // bend.set(-12.0) - rounding error too big + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.set(0) + assertEquals("46/46/13", getPitchBendStr(messages.get(i++), 0)); // bend.set(12.0) + assertEquals("4C/4C/25", getPitchBendStr(messages.get(i++), 0)); // bend.set(24.0) - rounding error too big + assertEquals("52/52/37", getPitchBendStr(messages.get(i++), 0)); // bend.set(36.0) - rounding error too big + assertEquals("58/58/49", getPitchBendStr(messages.get(i++), 0)); // bend.set(48.0) - rounding error too big + assertEquals("5E/5E/61", getPitchBendStr(messages.get(i++), 0)); // bend.set(60.0) - rounding error too big + + // double + assertEquals("22/00/-60", getPitchBendStr(messages.get(i++), 0)); // bend.double.set(-60.0) + assertEquals("28/00/-48", getPitchBendStr(messages.get(i++), 0)); // bend.double.set(-48.0) + assertEquals("2E/00/-36", getPitchBendStr(messages.get(i++), 0)); // bend.double.set(-36.0) + assertEquals("34/00/-24.0", getPitchBendStr(messages.get(i++), 1)); // bend.double.set(-24.0) + assertEquals("3A/00/-12.0", getPitchBendStr(messages.get(i++), 1)); // bend.double.set(-12.0) + assertEquals("40/00/0.00000", getPitchBendStr(messages.get(i++), 5)); // bend.double.set(0) + assertEquals("46/00/12.0", getPitchBendStr(messages.get(i++), 1)); // bend.double.set(12.0) + assertEquals("4C/00/24.0", getPitchBendStr(messages.get(i++), 1)); // bend.double.set(24.0) + assertEquals("52/00/36", getPitchBendStr(messages.get(i++), 0)); // bend.double.set(36.0) + assertEquals("58/00/48", getPitchBendStr(messages.get(i++), 0)); // bend.double.set(48.0) + assertEquals("5E/00/60", getPitchBendStr(messages.get(i++), 0)); // bend.double.set(60.0) + } + } + + parse(getWorkingFile("effects-3-at-port")); + // channel 0: mono_at + { + messages = getMessagesByStatus("D0"); + int i = 0; + + assertEquals("D0/00", getShortMsgBytesAsStr(messages.get(i++))); // mono_at.wait(4).set(0) + assertEquals("D0/40", getShortMsgBytesAsStr(messages.get(i++))); // mono_at.wait(4).set(64) + assertEquals("D0/7F", getShortMsgBytesAsStr(messages.get(i++))); // mono_at.wait(4).set(127) + assertEquals("D0/00", getShortMsgBytesAsStr(messages.get(i++))); // mono_at.wait(4).set(0%) + assertEquals("D0/40", getShortMsgBytesAsStr(messages.get(i++))); // mono_at.wait(4).set(50%) + assertEquals("D0/7F", getShortMsgBytesAsStr(messages.get(i++))); // mono_at.wait(4).set(100%) + + // .line(0%,100%) + for (int j = 0; j < 128; j++) { + String value = String.format("%02X", j); + assertEquals("D0/" + value, getShortMsgBytesAsStr(messages.get(i++))); + } + + // line(100%,0%) + for (int j = 127; j > -1; j--) { + String value = String.format("%02X", j); + assertEquals("D0/" + value, getShortMsgBytesAsStr(messages.get(i++))); + } + + // no further messages + assertEquals(messages.size(), i); + } + // channel 1: poly_at + { + messages = getMessagesByStatus("A1"); + int i = 0; + + // note c#-4 ==> 0D + assertEquals("A1/0D/00", getShortMsgBytesAsStr(messages.get(i++))); // poly_at.note(c#-4).wait.set(0) + assertEquals("A1/0D/40", getShortMsgBytesAsStr(messages.get(i++))); // .wait(4).set(64) + assertEquals("A1/0D/7F", getShortMsgBytesAsStr(messages.get(i++))); // .wait(4).set(127) + + // note c ==> 3C + assertEquals("A1/3C/00", getShortMsgBytesAsStr(messages.get(i++))); // poly_at.note(c).wait(4).set(0) + assertEquals("A1/3C/40", getShortMsgBytesAsStr(messages.get(i++))); // poly_at.note(c).wait(4).set(64) + assertEquals("A1/3C/7F", getShortMsgBytesAsStr(messages.get(i++))); // poly_at.note(c).wait(4).set(127) + + // note d ==> 3E + assertEquals("A1/3E/00", getShortMsgBytesAsStr(messages.get(i++))); // poly_at.note(d).wait(4).set(0%) + assertEquals("A1/3E/40", getShortMsgBytesAsStr(messages.get(i++))); // poly_at.note(d).wait(4).set(50%) + assertEquals("A1/3E/7F", getShortMsgBytesAsStr(messages.get(i++))); // poly_at.note(d).wait(4).set(100%) + + // poly_at.note(c).length(1).line(0%,100%) + for (int j = 0; j < 128; j++) { + String value = String.format("%02X", j); + assertEquals("A1/3C/" + value, getShortMsgBytesAsStr(messages.get(i++))); + } + + // poly_at.note(c).length(1).line(100%,0%) + for (int j = 127; j > -1; j--) { + String value = String.format("%02X", j); + assertEquals("A1/3C/" + value, getShortMsgBytesAsStr(messages.get(i++))); + } + + // no further messages + assertEquals(messages.size(), i); + } + // channel 2: poly_at with patterns (compact) + { + messages = getMessagesByStatus("A2"); + int i = 0; + + // c/d/e:pat1 + { + // .note(1) == .note(d); d == 62 == 0x3E + // .note(1).line(120,121) 120 == 0x78 + assertEquals("A2/3E/78", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/3E/79", getShortMsgBytesAsStr(messages.get(i++))); + + // : 0/1/2:pat2 + assertEquals("A2/3C/0A", getShortMsgBytesAsStr(messages.get(i++))); // .note(0) + assertEquals("A2/3C/0B", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/3E/0C", getShortMsgBytesAsStr(messages.get(i++))); // .note(1) + assertEquals("A2/3E/0D", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/40/0E", getShortMsgBytesAsStr(messages.get(i++))); // .note(2) + assertEquals("A2/40/0F", getShortMsgBytesAsStr(messages.get(i++))); + + // .note(2) == .note(e); d == 64 == 0x40 + // .note(2).line(122,123) + assertEquals("A2/40/7A", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/40/7B", getShortMsgBytesAsStr(messages.get(i++))); + + // : 0/1/2:pat2 + assertEquals("A2/3C/0A", getShortMsgBytesAsStr(messages.get(i++))); // .note(0) + assertEquals("A2/3C/0B", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/3E/0C", getShortMsgBytesAsStr(messages.get(i++))); // .note(1) + assertEquals("A2/3E/0D", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/40/0E", getShortMsgBytesAsStr(messages.get(i++))); // .note(2) + assertEquals("A2/40/0F", getShortMsgBytesAsStr(messages.get(i++))); + + // .note(0) == .note(c); c == 60 == 0x3C + // .note(0) + // .line(124,125) + assertEquals("A2/3C/7C", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/3C/7D", getShortMsgBytesAsStr(messages.get(i++))); + + // : 2/1/0:pat2 + assertEquals("A2/40/0A", getShortMsgBytesAsStr(messages.get(i++))); // .note(0) + assertEquals("A2/40/0B", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/3E/0C", getShortMsgBytesAsStr(messages.get(i++))); // .note(1) + assertEquals("A2/3E/0D", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A2/3C/0E", getShortMsgBytesAsStr(messages.get(i++))); // .note(2) + assertEquals("A2/3C/0F", getShortMsgBytesAsStr(messages.get(i++))); + } + + // no further messages + assertEquals(messages.size(), i); + } + // channel 3: poly_at with patterns (lowlevel) + { + messages = getMessagesByStatus("A3"); + int i = 0; + + // 2 c/d/e pat_ll_1 + { + // .note(1) == .note(d); d == 62 == 0x3E + // .note(1).line(120,121) 120 == 0x78 + assertEquals("A3/3E/78", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/3E/79", getShortMsgBytesAsStr(messages.get(i++))); + + // 0/1/2 pat_ll_2 + assertEquals("A3/3C/0A", getShortMsgBytesAsStr(messages.get(i++))); // .note(0) + assertEquals("A3/3C/0B", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/3E/0C", getShortMsgBytesAsStr(messages.get(i++))); // .note(1) + assertEquals("A3/3E/0D", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/40/0E", getShortMsgBytesAsStr(messages.get(i++))); // .note(2) + assertEquals("A3/40/0F", getShortMsgBytesAsStr(messages.get(i++))); + + // .note(2) == .note(e); d == 64 == 0x40 + // .note(2).line(122,123) + assertEquals("A3/40/7A", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/40/7B", getShortMsgBytesAsStr(messages.get(i++))); + + // 0/1/2 pat_ll_2 + assertEquals("A3/3C/0A", getShortMsgBytesAsStr(messages.get(i++))); // .note(0) + assertEquals("A3/3C/0B", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/3E/0C", getShortMsgBytesAsStr(messages.get(i++))); // .note(1) + assertEquals("A3/3E/0D", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/40/0E", getShortMsgBytesAsStr(messages.get(i++))); // .note(2) + assertEquals("A3/40/0F", getShortMsgBytesAsStr(messages.get(i++))); + + // .note(0) == .note(c); c == 60 == 0x3C + // .note(0) + // .line(124,125) + assertEquals("A3/3C/7C", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/3C/7D", getShortMsgBytesAsStr(messages.get(i++))); + + // 2/1/0 pat_ll_2 + assertEquals("A3/40/0A", getShortMsgBytesAsStr(messages.get(i++))); // .note(0) + assertEquals("A3/40/0B", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/3E/0C", getShortMsgBytesAsStr(messages.get(i++))); // .note(1) + assertEquals("A3/3E/0D", getShortMsgBytesAsStr(messages.get(i++))); + assertEquals("A3/3C/0E", getShortMsgBytesAsStr(messages.get(i++))); // .note(2) + assertEquals("A3/3C/0F", getShortMsgBytesAsStr(messages.get(i++))); + } + + // no further messages + assertEquals(messages.size(), i); + } + // channel 4: port_ctrl + { + messages = getMessagesByStatus("B4"); + int i = 0; + + // note c#-4 ==> 0D + assertEquals("4/c#-2", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(c#-2).wait.on() + assertEquals("4/c#+2", getPortCtrlStr(messages.get(i++))); // .wait(4).note(c#+2).on() + assertEquals("4/c#+2", getPortCtrlStr(messages.get(i++))); // .wait(4).on() + + // lower octave + assertEquals("4/c-", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(c-).on() + assertEquals("4/d-", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(d-)on() + assertEquals("4/e-", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(e-)on() + + // higher octave + assertEquals("4/c+", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(c+)on() + assertEquals("4/d+", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(d+)on() + assertEquals("4/e+", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(e+)on() + + assertEquals("4/c-3", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(c-3).on() + assertEquals("4/c+3", getPortCtrlStr(messages.get(i++))); // port_ctrl.note(c+3).on() + + // no further messages + assertEquals(messages.size(), i); + } + // channel 5: port_ctrl with patterns + { + messages = getMessagesByStatus("B5"); + int i = 0; + + // c/d/e:pat_p1 + { + assertEquals("5/d", getPortCtrlStr(messages.get(i++))); // .note(1).on() + + // 0/1/2:pat_p2 + { + assertEquals("5/c", getPortCtrlStr(messages.get(i++))); // .note(0).on() + assertEquals("5/d", getPortCtrlStr(messages.get(i++))); // .note(1).on() + assertEquals("5/e", getPortCtrlStr(messages.get(i++))); // .note(2).on() + } + assertEquals("5/e", getPortCtrlStr(messages.get(i++))); // .note(2).on() + + // 0/1/2:pat_p2 + { + assertEquals("5/c", getPortCtrlStr(messages.get(i++))); // .note(0).on() + assertEquals("5/d", getPortCtrlStr(messages.get(i++))); // .note(1).on() + assertEquals("5/e", getPortCtrlStr(messages.get(i++))); // .note(2).on() + } + + // .note(0) + // .on() + assertEquals("5/c", getPortCtrlStr(messages.get(i++))); // .note(2).on() + + // 2/1/0:pat_p2 + { + assertEquals("5/e", getPortCtrlStr(messages.get(i++))); // .note(0).on() + assertEquals("5/d", getPortCtrlStr(messages.get(i++))); // .note(1).on() + assertEquals("5/c", getPortCtrlStr(messages.get(i++))); // .note(2).on() + } + } + + // no further messages + assertEquals(messages.size(), i); + } } /** @@ -3462,11 +4035,31 @@ void testParseFilesFailing() { assertEquals( "0: note(c).vol.set(100%)", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_FL_EFF_NOT_SET) + "note")); - e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-flow-note-not-allowed")) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-flow-note-not-allowed-1")) ); assertEquals( 4, e.getLineNumber() ); assertEquals( "0: vol.note(c).set(100%)", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_FL_NOTE_NOT_SUPP) + "note")); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-flow-note-not-allowed-2")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( "0: mono_at.note(c).set(100%)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_FL_NOTE_NOT_SUPP) + "note")); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-flow-note-not-allowed-3")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( "0: pitch_bend_range.note(c).set(2.0)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_FL_NOTE_NOT_SUPP) + "note")); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-flow-note-not-set-1")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( "0: poly_at.set(100%)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_FL_NOTE_NOT_SET) + "set")); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-flow-note-not-set-2")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( "0: port_ctrl.on()", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_FL_NOTE_NOT_SET) + "on")); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-flow-numeric-for-none")) ); assertEquals( 4, e.getLineNumber() ); assertEquals( "0: ctrl=123.wait.set(12)", e.getLineContent() ); @@ -3492,6 +4085,30 @@ void testParseFilesFailing() { assertEquals( "0: vol.double.set(30/128)", e.getLineContent() ); assertTrue( e.getMessage().startsWith(String.format(Dict.get(Dict.ERROR_FUNC_LSB_TOO_HIGH), "30/128", "128"))); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-pattern-index-invalid-1")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( ": poly_at.note(3)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(String.format(Dict.get(Dict.ERROR_FL_NOTE_PAT_IDX_TOO_HIGH), "3", ".note(3)"))); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-pattern-index-invalid-2")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( ": poly_at.note(c)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(String.format(Dict.get(Dict.ERROR_FL_NOTE_PAT_IDX_NAN), "c", ".note(c)"))); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-pattern-index-invalid-3")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( ": poly_at.note(3)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(String.format(Dict.get(Dict.ERROR_FL_NOTE_PAT_IDX_TOO_HIGH), "3", ".note(3)"))); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-pattern-index-invalid-4")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( ": poly_at.note(3)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(String.format(Dict.get(Dict.ERROR_FL_NOTE_PAT_IDX_TOO_HIGH), "3", ".note(3)"))); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("eff-pattern-index-invalid-5")) ); + assertEquals( 4, e.getLineNumber() ); + assertEquals( ": poly_at.note(3)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(String.format(Dict.get(Dict.ERROR_FL_NOTE_PAT_IDX_TOO_HIGH), "3", ".note(3)"))); } /** @@ -3658,4 +4275,90 @@ private static ArrayList getNoteOnOffMessagesByChannel(int channe private static String getLyrics() { return (String) KaraokeAnalyzer.getKaraokeInfo().get("lyrics_full"); } + + /** + * Calculates and returns a summary of a short message without (without tickstamp). + * + * The summary consists of: + * + * - Status byte in hex + * - first data byte in hex (if available) + * - second data byte in hex (if available) + * + * All bytes are separated by slash (/). + * + * @param msg the message + * @return the summary. + */ + private static String getShortMsgBytesAsStr(SingleMessage msg) { + + // get MSB and LSB + byte[] bytes = msg.getMessageBytes(); + String result = String.format("%02X", bytes[0]); + if (bytes.length > 1) + result += "/" + String.format("%02X", bytes[1]); + if (bytes.length > 2) + result += "/" + String.format("%02X", bytes[2]); + + return result; + } + + /** + * Calculates and returns a summary from a pitch bend message. + * + * The summary consists of: + * + * - MSB + * - LSB + * - resulting bend in half tones (rounded) + * + * @param msg the message + * @param decimalPlaces number of decimal places to round the half tones + * @return the summary. + */ + private static String getPitchBendStr(SingleMessage msg, int decimalPlaces) { + + // format half tones + String summary = msg.getDistinctOptions(IMessageType.OPT_SUMMARY); + float halfTones = Float.parseFloat(summary); + String formatted = String.format("%." + decimalPlaces + "f", halfTones); + + // get MSB and LSB + byte[] bytes = msg.getMessageBytes(); + String msb = String.format("%02X", bytes[2]); + String lsb = String.format("%02X", bytes[1]); + + return msb + "/" + lsb + "/" + formatted; + } + + /** + * Calculates and returns a summary from a portamento control message. + * + * Throws an exception if the message is not a portamento control message. + * + * The summary consists of: + * + * - channel + * - note name + * + * @param msg the message + * @return the summary. + */ + private static String getPortCtrlStr(SingleMessage msg) { + + String note = msg.getDistinctOptions(IMessageType.OPT_SUMMARY); + + byte[] bytes = msg.getMessageBytes(); + String status = String.format("%02X", bytes[0]); + int channel = bytes[0] & 0x0F; + String ctrl = String.format("%02X", bytes[1]); + + // is it a portamento ctrl? + if (!status.startsWith("B")) + throw new RuntimeException("not a ctrl change"); + if (!ctrl.startsWith("54")) + throw new RuntimeException("not a portamento ctrl message"); + + return channel + "/" + note; + } } diff --git a/test/org/midica/testfiles/failing/eff-flow-note-not-allowed.midica b/test/org/midica/testfiles/failing/eff-flow-note-not-allowed-1.midica similarity index 100% rename from test/org/midica/testfiles/failing/eff-flow-note-not-allowed.midica rename to test/org/midica/testfiles/failing/eff-flow-note-not-allowed-1.midica diff --git a/test/org/midica/testfiles/failing/eff-flow-note-not-allowed-2.midica b/test/org/midica/testfiles/failing/eff-flow-note-not-allowed-2.midica new file mode 100644 index 0000000..65204a0 --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-flow-note-not-allowed-2.midica @@ -0,0 +1,4 @@ +INCLUDE inc/instruments.midica + + +0: mono_at.note(c).set(100%) diff --git a/test/org/midica/testfiles/failing/eff-flow-note-not-allowed-3.midica b/test/org/midica/testfiles/failing/eff-flow-note-not-allowed-3.midica new file mode 100644 index 0000000..213399b --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-flow-note-not-allowed-3.midica @@ -0,0 +1,4 @@ +INCLUDE inc/instruments.midica + + +0: pitch_bend_range.note(c).set(2.0) diff --git a/test/org/midica/testfiles/failing/eff-flow-note-not-set-1.midica b/test/org/midica/testfiles/failing/eff-flow-note-not-set-1.midica new file mode 100644 index 0000000..17498d5 --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-flow-note-not-set-1.midica @@ -0,0 +1,4 @@ +INCLUDE inc/instruments.midica + + +0: poly_at.set(100%) diff --git a/test/org/midica/testfiles/failing/eff-flow-note-not-set-2.midica b/test/org/midica/testfiles/failing/eff-flow-note-not-set-2.midica new file mode 100644 index 0000000..045ba1a --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-flow-note-not-set-2.midica @@ -0,0 +1,4 @@ +INCLUDE inc/instruments.midica + + +0: port_ctrl.on() diff --git a/test/org/midica/testfiles/failing/eff-pattern-index-invalid-1.midica b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-1.midica new file mode 100644 index 0000000..6ba6414 --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-1.midica @@ -0,0 +1,11 @@ +INCLUDE inc/instruments.midica + + +0: c/d/e:pat + + +PATTERN pat + : 0/1/2 + : poly_at.note(3) + : 0/1/2 +END diff --git a/test/org/midica/testfiles/failing/eff-pattern-index-invalid-2.midica b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-2.midica new file mode 100644 index 0000000..bdf6b4a --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-2.midica @@ -0,0 +1,11 @@ +INCLUDE inc/instruments.midica + + +0: c/d/e:pat + + +PATTERN pat + : 0/1/2 + : poly_at.note(c) + : 0/1/2 +END diff --git a/test/org/midica/testfiles/failing/eff-pattern-index-invalid-3.midica b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-3.midica new file mode 100644 index 0000000..4a0d4e4 --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-3.midica @@ -0,0 +1,13 @@ +INCLUDE inc/instruments.midica + + +0: c/d/e:pat + + +PATTERN pat + { + : 0/1/2 + : poly_at.note(3) + : 0/1/2 + } +END diff --git a/test/org/midica/testfiles/failing/eff-pattern-index-invalid-4.midica b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-4.midica new file mode 100644 index 0000000..f29f98c --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-4.midica @@ -0,0 +1,15 @@ +INCLUDE inc/instruments.midica + + +0: c/d/e:pat + + +PATTERN pat + { + { + : 0/1/2 + : poly_at.note(3) + : 0/1/2 + } + } +END diff --git a/test/org/midica/testfiles/failing/eff-pattern-index-invalid-5.midica b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-5.midica new file mode 100644 index 0000000..10f189e --- /dev/null +++ b/test/org/midica/testfiles/failing/eff-pattern-index-invalid-5.midica @@ -0,0 +1,25 @@ +INCLUDE inc/instruments.midica + + +0: c/d/e:pat1 + + +PATTERN pat1 + { + { + : 0/1/2 + : 0/1/2:pat2 + : 0/1/2 + } + } +END + +PATTERN pat2 + { + { + : 0/1/2 + : poly_at.note(3) + : 0/1/2 + } + } +END diff --git a/test/org/midica/testfiles/working/effects-1.midica b/test/org/midica/testfiles/working/effects-1-set.midica similarity index 100% rename from test/org/midica/testfiles/working/effects-1.midica rename to test/org/midica/testfiles/working/effects-1-set.midica diff --git a/test/org/midica/testfiles/working/effects-2-bend.midica b/test/org/midica/testfiles/working/effects-2-bend.midica new file mode 100644 index 0000000..6b66f4b --- /dev/null +++ b/test/org/midica/testfiles/working/effects-2-bend.midica @@ -0,0 +1,418 @@ +INCLUDE inc/instruments.midica + +INSTRUMENT 0 VIOLIN +INSTRUMENT 1 VIOLIN + +//////////////////////////////////////// +// channel 0: RPN / MSB (pitch bend range) +//////////////////////////////////////// + +0: pitch_bend_range.length(32).wait.set(0.0) +0: .wait.set(127.0) +0: .wait.set(12.7) +0: .wait.set(12.0) +0: .wait.set(127) + +// double +0: .double.wait.set(0.0) +0: .wait.set(127/5) +0: .wait.set(124.998) +0: .wait.set(4.997) +0: .wait.set(127.990) +0: .wait.set(-0.000001) +0: .wait.set(127) + +// generic RPN +0: c c +0: rpn=0/0.set(96.7) +0: c +0: rpn=0/0.double.set(96.7) +0: c +0: rpn=0.set(2.0) +0: c +0: rpn=0.double.set(2.0) + + +//////////////////////////////////////// +// channel 1: pitch bend +//////////////////////////////////////// + +1: pitch_bend_range.set(2.0) c:32 (d=100%) + +// percentage values +///////////////////// + +// single byte + 1: (l=range:_2.0\c_percentage\c_single:\r) - + 1: bend.set(-100%) (l=-100%_c_) c +// 1: bend.set(0) (l=0_a#_) a#- + 1: bend.set(-50%) (l=-50%_c_) c +// 1: bend.set(0) (l=0_b_) b- + 1: bend.set(0%) (l=0%_c_) c + 1: bend.set(50%) (l=50%_c_) c +// 1: bend.set(0) (l=0_c#_) c# + 1: bend.set(100%) (l=100%_c_) c +// 1: bend.set(0) (l=0_d_) d + +// double + 1: (l=\rrange:_2.0\c_percentage\c_double:\r) - + 1: bend.double.set(-100%) (l=-100%_c_) c +// 1: bend.double.set(0) (l=0_a#_) a#- + 1: bend.double.set(-50%) (l=-50%_c_) c +// 1: bend.double.set(0) (l=0_b_) b- + 1: bend.double.set(0%) (l=0%_c_) c + 1: bend.double.set(50%) (l=50%_c_) c +// 1: bend.double.set(0) (l=0_c#_) c# + 1: bend.double.set(100%) (l=100%_c_) c +// 1: bend.double.set(0) (l=0_d_) d + +// MSB / LSB +///////////////////// + +1: (l=\rMSB/LSB\r) - + +// only double allowed +1: bend.double.set(0/0) (l=0/0_c_) c +1: bend.double.set(64/0) (l=64/0_c_) c +1: bend.double.set(127/127) (l=127/127_c_) c + +///////////////////// +// half tone steps +///////////////////// + +1: (l=\nHALF_TONE_STEPS:) - + +// 2.7 +///////////////////// +1: pitch_bend_range.set(2.7) - + +// single byte + 1: (l=\r2.7\c_single:\r) - + 1: bend.set(-2.0) (l=-2.0_c_) c +// 1: bend.set(0) (l=0_a#_) a#- + 1: bend.set(-1.0) (l=-1.0_c_) c +// 1: bend.set(0) (l=0_b_) b- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(1.0) (l=1.0_c_) c +// 1: bend.set(0) (l=0_c#_) c# + 1: bend.set(2.0) (l=2.0_c_) c +// 1: bend.set(0) (l=0_d_) d + + +// double + 1: (l=\r2.7\c_double:\r) - + 1: bend.double.set(-2.0) (l=-2.0_c_) c +// 1: bend.double.set(0) (l=0_a#_) a#- + 1: bend.double.set(-1) (l=-1_c_) c +// 1: bend.double.set(0) (l=0_b_) b- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(1.0) (l=1.0_c_) c +// 1: bend.double.set(0) (l=0_c#_) c# + 1: bend.double.set(2) (l=2_c_) c +// 1: bend.double.set(0) (l=0_d_) d + +// 4.0 +///////////////////// +1: pitch_bend_range.set(4.0) - + +// single byte + 1: (l=\r4.0\c_single:\r) - + 1: bend.set(-4.0) (l=-4.0_c_) c +// 1: bend.set(0) (l=0_g#-_) g#- + 1: bend.set(-2) (l=-2_c_) c +// 1: bend.set(0) (l=0_a#_) a#- + 1: bend.set(-1.0) (l=-1.0_c_) c +// 1: bend.set(0) (l=0_b_) b- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(1.0) (l=1.0_c_) c +// 1: bend.set(0) (l=0_c#_) c# + 1: bend.set(2.0) (l=2.0_c_) c +// 1: bend.set(0) (l=0_d_) d + 1: bend.set(4) (l=4_c_) c +// 1: bend.set(0) (l=0_e_) e + + +// double + 1: (l=\r4.0\c_double:\r) - + 1: bend.double.set(-4.0) (l=-4.0_c_) c +// 1: bend.double.set(0) (l=0_g#-_) g#- + 1: bend.double.set(-2.0) (l=-2.0_c_) c +// 1: bend.double.set(0) (l=0_a#_) a#- + 1: bend.double.set(-1.0) (l=-1.0_c_) c +// 1: bend.double.set(0) (l=0_b_) b- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(1.0) (l=1.0_c_) c +// 1: bend.double.set(0) (l=0_c#_) c# + 1: bend.double.set(2.0) (l=2.0_c_) c +// 1: bend.double.set(0) (l=0_d_) d + 1: bend.double.set(4.0) (l=4.0_c_) c +// 1: bend.double.set(0) (l=0_e_) e + +// 8.0 +///////////////////// +1: pitch_bend_range.set(8.0) - + +// single byte + 1: (l=\r8.0\c_single:\r) - + 1: bend.set(-8.0) (l=-8.0_c_) c +// 1: bend.set(0) (l=0_e-_) e- + 1: bend.set(-2.0) (l=-2.0_c_) c +// 1: bend.set(0) (l=0_a#_) a#- + 1: bend.set(-1.0) (l=-1.0_c_) c +// 1: bend.set(0) (l=0_b_) b- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(1.0) (l=1.0_c_) c +// 1: bend.set(0) (l=0_c#_) c# + 1: bend.set(2.0) (l=2.0_c_) c +// 1: bend.set(0) (l=0_d_) d + 1: bend.set(8.0) (l=8.0_c_) c +// 1: bend.set(0) (l=0_g#_) g# + +// double + 1: (l=\r8.0\c_double:\r) - + 1: bend.double.set(-8.0) (l=-8.0_c_) c +// 1: bend.double.set(0) (l=0_e-_) e- + 1: bend.double.set(-2.0) (l=-2.0_c_) c +// 1: bend.double.set(0) (l=0_a#_) a#- + 1: bend.double.set(-1.0) (l=-1.0_c_) c +// 1: bend.double.set(0) (l=0_b_) b- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(1.0) (l=1.0_c_) c +// 1: bend.double.set(0) (l=0_c#_) c# + 1: bend.double.set(2.0) (l=2.0_c_) c +// 1: bend.double.set(0) (l=0_d_) d + 1: bend.double.set(8.0) (l=8.0_c_) c +// 1: bend.double.set(0) (l=0_g#_) g# + +// 12.0 +///////////////////// +1: pitch_bend_range.set(12.0) - + +// single byte + 1: (l=\r12.0\c_single:\r) - + 1: bend.set(-12.0) (l=-12.0_c_) c +// 1: bend.set(0) (l=0_c-_) c- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(12.0) (l=12.0_c_) c +// 1: bend.set(0) (l=0_c+_) c+ + +// double + 1: (l=\r12.0\c_double:\r) - + 1: bend.double.set(-12.0) (l=-12.0_c_) c +// 1: bend.set(0) (l=0_c-_) c- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(12.0) (l=12.0_c_) c +// 1: bend.double.set(0) (l=0_c+_) c+ + +// 24.0 +///////////////////// +1: pitch_bend_range.set(24.0) - + +// single byte + 1: (l=\r24.0\c_single:\r) - + 1: bend.set(-24.0) (l=-24.0_c_) c +// 1: bend.set(0) (l=0_c-2_) c-2 + 1: bend.set(-12.0) (l=-12.0_c_) c +// 1: bend.set(0) (l=0_c-_) c- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(12.0) (l=12.0_c_) c +// 1: bend.set(0) (l=0_c+_) c+ + 1: bend.set(24.0) (l=24.0_c_) c +// 1: bend.set(0) (l=0_c+2_) c+2 + +// double + 1: (l=\r24.0\c_double:\r) - + 1: bend.double.set(-24.0) (l=-24.0_c_) c +// 1: bend.double.set(0) (l=0_c-2_) c-2 + 1: bend.double.set(-12.0) (l=-12.0_c_) c +// 1: bend.double.set(0) (l=0_c-_) c- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(12.0) (l=12.0_c_) c +// 1: bend.double.set(0) (l=0_c+_) c+ + 1: bend.double.set(24.0) (l=24.0_c_) c +// 1: bend.double.set(0) (l=0_c+2_) c+2 + +// 36.0 +///////////////////// +1: pitch_bend_range.set(36.0) - + +// single byte + 1: (l=\r36.0\c_single:\r) - + 1: bend.set(-36.0) (l=-36.0_c_) c +// 1: bend.set(0) (l=0_c-3_) c-3 + 1: bend.set(-24.0) (l=-24.0_c_) c +// 1: bend.set(0) (l=0_c-2_) c-2 + 1: bend.set(-12.0) (l=-12.0_c_) c +// 1: bend.set(0) (l=0_c-_) c- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(12.0) (l=12.0_c_) c +// 1: bend.set(0) (l=0_c+_) c+ + 1: bend.set(24.0) (l=24.0_c_) c +// 1: bend.set(0) (l=0_c+2_) c+2 + 1: bend.set(36.0) (l=36.0_c_) c +// 1: bend.set(0) (l=0_c+3_) c+3 + +// double + 1: (l=\r36.0\c_double:\r) - + 1: bend.double.set(-36.0) (l=-36.0_c_) c +// 1: bend.double.set(0) (l=0_c-3_) c-3 + 1: bend.double.set(-24.0) (l=-24.0_c_) c +// 1: bend.double.set(0) (l=0_c-2_) c-2 + 1: bend.double.set(-12.0) (l=-12.0_c_) c +// 1: bend.double.set(0) (l=0_c-_) c- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(12.0) (l=12.0_c_) c +// 1: bend.double.set(0) (l=0_c+_) c+ + 1: bend.double.set(24.0) (l=24.0_c_) c +// 1: bend.double.set(0) (l=0_c+2_) c+2 + 1: bend.double.set(36.0) (l=36.0_c_) c +// 1: bend.double.set(0) (l=0_c+3_) c+3 + +// 48.0 +///////////////////// +1: pitch_bend_range.set(48.0) - + +// single byte + 1: (l=\r48.0\c_single:\r) - + 1: bend.set(-48.0) (l=-48.0_c_) c +// 1: bend.set(0) (l=0_c-3_) c-4 + 1: bend.set(-36.0) (l=-36.0_c_) c +// 1: bend.set(0) (l=0_c-3_) c-3 + 1: bend.set(-24.0) (l=-24.0_c_) c +// 1: bend.set(0) (l=0_c-2_) c-2 + 1: bend.set(-12.0) (l=-12.0_c_) c +// 1: bend.set(0) (l=0_c-_) c- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(12.0) (l=12.0_c_) c +// 1: bend.set(0) (l=0_c+_) c+ + 1: bend.set(24.0) (l=24.0_c_) c +// 1: bend.set(0) (l=0_c+2_) c+2 + 1: bend.set(36.0) (l=36.0_c_) c +// 1: bend.set(0) (l=0_c+3_) c+3 + 1: bend.set(48.0) (l=48.0_c_) c +// 1: bend.set(0) (l=0_c+4_) c+4 + +// double + 1: (l=\r48.0\c_double:\r) - + 1: bend.double.set(-48.0) (l=-48.0_c_) c +// 1: bend.double.set(0) (l=0_c-3_) c-4 + 1: bend.double.set(-36.0) (l=-36.0_c_) c +// 1: bend.double.set(0) (l=0_c-3_) c-3 + 1: bend.double.set(-24.0) (l=-24.0_c_) c +// 1: bend.double.set(0) (l=0_c-2_) c-2 + 1: bend.double.set(-12.0) (l=-12.0_c_) c +// 1: bend.double.set(0) (l=0_c-_) c- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(12.0) (l=12.0_c_) c +// 1: bend.double.set(0) (l=0_c+_) c+ + 1: bend.double.set(24.0) (l=24.0_c_) c +// 1: bend.double.set(0) (l=0_c+2_) c+2 + 1: bend.double.set(36.0) (l=36.0_c_) c +// 1: bend.double.set(0) (l=0_c+3_) c+3 + 1: bend.double.set(48.0) (l=48.0_c_) c +// 1: bend.double.set(0) (l=0_c+4_) c+4 + +// 60.0 +///////////////////// +1: pitch_bend_range.set(60.0) - + +// single byte + 1: (l=\r60.0\c_single:\r) - + 1: bend.set(-60.0) (l=-60.0_c_) c +// 1: bend.set(0) (l=0_c-5_) c-5 + 1: bend.set(-48.0) (l=-48.0_c_) c +// 1: bend.set(0) (l=0_c-3_) c-4 + 1: bend.set(-36.0) (l=-36.0_c_) c +// 1: bend.set(0) (l=0_c-3_) c-3 + 1: bend.set(-24.0) (l=-24.0_c_) c +// 1: bend.set(0) (l=0_c-2_) c-2 + 1: bend.set(-12.0) (l=-12.0_c_) c +// 1: bend.set(0) (l=0_c-_) c- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(12.0) (l=12.0_c_) c +// 1: bend.set(0) (l=0_c+_) c+ + 1: bend.set(24.0) (l=24.0_c_) c +// 1: bend.set(0) (l=0_c+2_) c+2 + 1: bend.set(36.0) (l=36.0_c_) c +// 1: bend.set(0) (l=0_c+3_) c+3 + 1: bend.set(48.0) (l=48.0_c_) c +// 1: bend.set(0) (l=0_c+4_) c+4 + 1: bend.set(60.0) (l=60.0_c_) c +// 1: bend.set(0) (l=0_c+5_) c+5 + +// double + 1: (l=\r60.0\c_double:\r) - + 1: bend.double.set(-60.0) (l=-60.0_c_) c +// 1: bend.double.set(0) (l=0_c-5_) c-5 + 1: bend.double.set(-48.0) (l=-48.0_c_) c +// 1: bend.double.set(0) (l=0_c-3_) c-4 + 1: bend.double.set(-36.0) (l=-36.0_c_) c +// 1: bend.double.set(0) (l=0_c-3_) c-3 + 1: bend.double.set(-24.0) (l=-24.0_c_) c +// 1: bend.double.set(0) (l=0_c-2_) c-2 + 1: bend.double.set(-12.0) (l=-12.0_c_) c +// 1: bend.double.set(0) (l=0_c-_) c- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(12.0) (l=12.0_c_) c +// 1: bend.double.set(0) (l=0_c+_) c+ + 1: bend.double.set(24.0) (l=24.0_c_) c +// 1: bend.double.set(0) (l=0_c+2_) c+2 + 1: bend.double.set(36.0) (l=36.0_c_) c +// 1: bend.double.set(0) (l=0_c+3_) c+3 + 1: bend.double.set(48.0) (l=48.0_c_) c +// 1: bend.double.set(0) (l=0_c+4_) c+4 + 1: bend.double.set(60.0) (l=60.0_c_) c +// 1: bend.double.set(0) (l=0_c+5_) c+5 + +// 127.99 +///////////////////// +1: pitch_bend_range.double.set(127.99) - + +// single byte + 1: (l=\r127.99\c_single:\r) - + 1: bend.set(-60.0) (l=-60.0_c_) c +// 1: bend.set(0) (l=0_c-5_) c-5 + 1: bend.set(-48.0) (l=-48.0_c_) c +// 1: bend.set(0) (l=0_c-3_) c-4 + 1: bend.set(-36.0) (l=-36.0_c_) c +// 1: bend.set(0) (l=0_c-3_) c-3 + 1: bend.set(-24.0) (l=-24.0_c_) c +// 1: bend.set(0) (l=0_c-2_) c-2 + 1: bend.set(-12.0) (l=-12.0_c_) c +// 1: bend.set(0) (l=0_c-_) c- + 1: bend.set(0) (l=0_c_) c + 1: bend.set(12.0) (l=12.0_c_) c +// 1: bend.set(0) (l=0_c+_) c+ + 1: bend.set(24.0) (l=24.0_c_) c +// 1: bend.set(0) (l=0_c+2_) c+2 + 1: bend.set(36.0) (l=36.0_c_) c +// 1: bend.set(0) (l=0_c+3_) c+3 + 1: bend.set(48.0) (l=48.0_c_) c +// 1: bend.set(0) (l=0_c+4_) c+4 + 1: bend.set(60.0) (l=60.0_c_) c +// 1: bend.set(0) (l=0_c+5_) c+5 + +// double + 1: (l=\r127.99\c_double:\r) - + 1: bend.double.set(-60.0) (l=-60.0_c_) c +// 1: bend.double.set(0) (l=0_c-5_) c-5 + 1: bend.double.set(-48.0) (l=-48.0_c_) c +// 1: bend.double.set(0) (l=0_c-3_) c-4 + 1: bend.double.set(-36.0) (l=-36.0_c_) c +// 1: bend.double.set(0) (l=0_c-3_) c-3 + 1: bend.double.set(-24.0) (l=-24.0_c_) c +// 1: bend.double.set(0) (l=0_c-2_) c-2 + 1: bend.double.set(-12.0) (l=-12.0_c_) c +// 1: bend.double.set(0) (l=0_c-_) c- + 1: bend.double.set(0) (l=0_c_) c + 1: bend.double.set(12.0) (l=12.0_c_) c +// 1: bend.double.set(0) (l=0_c+_) c+ + 1: bend.double.set(24.0) (l=24.0_c_) c +// 1: bend.double.set(0) (l=0_c+2_) c+2 + 1: bend.double.set(36.0) (l=36.0_c_) c +// 1: bend.double.set(0) (l=0_c+3_) c+3 + 1: bend.double.set(48.0) (l=48.0_c_) c +// 1: bend.double.set(0) (l=0_c+4_) c+4 + 1: bend.double.set(60.0) (l=60.0_c_) c +// 1: bend.double.set(0) (l=0_c+5_) c+5 + diff --git a/test/org/midica/testfiles/working/effects-3-at-port.midica b/test/org/midica/testfiles/working/effects-3-at-port.midica new file mode 100644 index 0000000..f78ce32 --- /dev/null +++ b/test/org/midica/testfiles/working/effects-3-at-port.midica @@ -0,0 +1,220 @@ +INCLUDE inc/instruments.midica + +INSTRUMENT 0 VIOLIN +INSTRUMENT 1 VIOLIN +INSTRUMENT 2 VIOLIN +INSTRUMENT 3 VIOLIN +INSTRUMENT 4 VIOLIN +INSTRUMENT 5 VIOLIN +INSTRUMENT 6 VIOLIN +INSTRUMENT 7 VIOLIN + +//////////////////////////////////////////////////////////// +// channel 0: monophonic aftertouch (channel aftertouch) +//////////////////////////////////////////////////////////// + +0: (l=MONO_AT:\r) -:8 +{ + 0: mono_at.wait(4).set(0) (l=0_) -:4 + 0: mono_at.wait(4).set(64) (l=64_) - + 0: mono_at.wait(4).set(127) (l=127_) - + + // percent + 0: mono_at.wait(4).set(0%) (l=0%_) - + 0: mono_at.wait(4).set(50%) (l=50%_) - + 0: mono_at.wait(4).set(100%) (l=100%\r) - +} m +0: c/e/g:1+2 +* +0: (l=line(0%\c100%)\r) mono_at.length(1).line(0%,100%) c:1 +* +0: (l=line(100%\c0%)\r) mono_at.length(1).line(100%,0%) c:1 +* +//////////////////////////////////////////////////////////// +// channel 1: polyphonic aftertouch +//////////////////////////////////////////////////////////// + +* +1: (l=POLY_AT:\r) -:8 + +1: poly_at.note(c#-4).wait.set(0) +1: .wait(4).set(64) +1: .wait(4).set(127) +1: c:2. +{ + { + 1: poly_at.note(c).wait(4).set(0) (l=0_) -:8 + 1: poly_at.note(c).wait(4).set(64) (l=64_) - + 1: poly_at.note(c).wait(4).set(127) (l=127_) - + + // percent + 1: poly_at.note(d).wait(4).set(0%) (l=0%_) - + 1: poly_at.note(d).wait(4).set(50%) (l=50%_) - + 1: poly_at.note(d).wait(4).set(100%) (l=100%\r) - + } m +} +1: c/e/g:1+1 + + +1: (l=line(0%\c100%)\r) poly_at.note(c).length(1).line(0%,100%) c:1 +1: (l=line(100%\c0%)\r) poly_at.note(c).length(1).line(100%,0%) c:1 + +//////////////////////////////////////////////////////////// +// channel 2: poly_at in patterns with note index +//////////////////////////////////////////////////////////// + +* +2: (l=POLY_AT-patterns:\r) -:8 +2: (l=c/d/e:pat1) c/d/e:pat1 + +PATTERN pat1 + : 0:8 poly_at.length(2).note(1).line(120,121) 0 1 2 0 + : 0/1/2:pat2 + { + : 0:8 poly_at.note(0).wait.note(1).note(2).line(122,123) 0 1 2 0 + : 0/1/2:pat2 + { + : 0:8 poly_at.note(2).wait.note(1).note(0) + : .line(124,125) 0 1 2 0 + : 2/1/0:pat2 + } + } +END + +PATTERN pat2 + { + { + : 0:8 poly_at.note(0).line(10,11) 0 1 2 0 + } + : 0:8 poly_at.note(1).line(12,13) 0 1 2 0 + } + : 0:8 poly_at.note(2).line(14,15) 0 1 2 0 +END + +//////////////////////////////////////////////////////////// +// channel 3: poly_at in patterns with note index +//////////////////////////////////////////////////////////// + +* +3 - 8 l=POLY_AT-patterns:\r +3 c/d/e pat_ll_1 l=c/d/e:pat_ll_1\r + +PATTERN pat_ll_1 + 0 8 + poly_at.length(2).note(1).line(120,121) 8 + 0 8 + 1 8 + 2 8 + 0 8 + 0/1/2 pat_ll_2 + { + 0 8 + poly_at.length(2).note(2).line(122,123) 8 + 0 8 + 1 8 + 2 8 + 0 8 + 0/1/2 pat_ll_2 + { + 0 8 + poly_at.length(2).note(0) 8 + .line(124,125) 8 + 0 8 + 1 8 + 2 8 + 0 8 + 2/1/0 pat_ll_2 + } + } +END + +PATTERN pat_ll_2 + { + { + 0 8 + poly_at.note(0).line(10,11) 8 + 0 8 + 1 8 + 2 8 + 0 8 + } + 0 8 + poly_at.note(1).line(12,13) 8 + 0 8 + 1 8 + 2 8 + 0 8 + } + 0 8 + poly_at.note(2).line(14,15) 8 + 0 8 + 1 8 + 2 8 + 0 8 +END + +//////////////////////////////////////////////////////////// +// channel 4: portamento control +//////////////////////////////////////////////////////////// + +* +4: (l=PORT_CTRL:\r) -:8 + +4: port_ctrl.note(c#-2).wait.on() +4: .wait().note(c#+2).on() +4: .wait().on() +4: -:2 c c c -:8 +{ + { + // lower octave + 4: port_ctrl.note(c-).on() - c (l=c-_) - + 4: port_ctrl.note(d-).on() - c (l=d-_) - + 4: port_ctrl.note(e-).on() - c (l=e-_) - + + // higher octave + 4: port_ctrl.note(c+).on() - c (l=c+_) - + 4: port_ctrl.note(d+).on() - c (l=d+_) - + 4: port_ctrl.note(e+).on() - c (l=e+\r) - + } +} +4: - +4: (l=c-\r) port_ctrl.note(c-3).on() c:1 +4: (l=c+\r) port_ctrl.note(c+3).on() c:1 + +//////////////////////////////////////////////////////////// +// channel 5: port_ctrl in patterns with note index +//////////////////////////////////////////////////////////// + +* +5: (l=PORT_CTRL-patterns:\r) -:8 +5: (l=c/d/e:pat_p1) c/d/e:pat_p1 + +PATTERN pat_p1 + : 0:8 port_ctrl.note(1).on() 0 1 2 0 + : 0/1/2:pat_p2 + { + : 0:8 port_ctrl.note(0).wait.note(1).note(2).on() 0 1 2 0 + : 0/1/2:pat_p2 + { + : 0:8 port_ctrl.note(2).wait.note(1).note(0) + : .on() 0 1 2 0 + : 2/1/0:pat_p2 + } + } +END + +PATTERN pat_p2 + { + { + : 0:8 port_ctrl.note(0).on() 0 1 2 0 + } + : 0:8 port_ctrl.note(1).on() 0 1 2 0 + } + : 0:8 port_ctrl.note(2).on() 0 1 2 0 +END + + + + + +