/* * JFugue, an Application Programming Interface (API) for Music Programming * http://www.jfugue.org * * Copyright (C) 2003-2014 David Koelle * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.staccato; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * The MicrotonePreprocess lets a user express a microtone * using 'm' followed by the frequency - e.g., m440. The MicrotonePreprocessor takes this String, * parses the frequency value, figures out what Pitch Wheel and Note events need to be called to * generate this frequency in MIDI, and returns the full set of Staccato Pitch Wheel and Note * events. */ public class MicrotonePreprocessor implements Preprocessor { private static MicrotonePreprocessor instance; public static MicrotonePreprocessor getInstance() { if (instance == null) { instance = new MicrotonePreprocessor(); } return instance; } private static Pattern microtonePattern = Pattern.compile("(^|\\s)[Mm]\\S+"); private static Pattern frequencyPattern = Pattern.compile("[0-9.]+"); private static Pattern qualifierPattern = Pattern.compile("[WHQISTXOADwhqistxoad/]+[0-9.]*\\S*"); @Override public String preprocess(String s, StaccatoParserContext context) { StringBuilder buddy = new StringBuilder(); int posPrev = 0; Matcher m = microtonePattern.matcher(s); while (m.find()) { buddy.append(s, posPrev, m.start()); double frequency = 0.0d; Matcher frequencyMatcher = frequencyPattern.matcher(m.group()); if (frequencyMatcher.find()) { frequency = Double.parseDouble(frequencyMatcher.group()); } else { throw new IllegalArgumentException("The following is not a valid microtone frequency: "+frequencyMatcher.group()); } String qualifier = null; Matcher qualifierMatcher = qualifierPattern.matcher(m.group()); if (qualifierMatcher.find()) { qualifier = qualifierMatcher.group(); } if (qualifier == null) { qualifier = "/" + DefaultNoteSettingsManager.getInstance().getDefaultDuration(); } buddy.append(" "); buddy.append(convertFrequencyToStaccato(frequency, qualifier)); posPrev = m.end(); } buddy.append(s.substring(posPrev, s.length())); return buddy.toString().trim(); } /** * Converts the given frequency to a music string that involves * the Pitch Wheel and notes to create the frequency * @param freq the frequency * @return a MusicString that represents the frequency */ public static String convertFrequencyToStaccato(double frequency, String qualifier) { double totalCents = 1200 * Math.log(frequency / 16.3515978312876) / Math.log(2); double octave = Math.round(totalCents / 1200.0); double semitoneCents = totalCents - (octave * 1200.0); double semitone = Math.round(semitoneCents / 100.0); double microtonalAdjustment = semitoneCents - (semitone * 100.0); double pitches = 8192.0 + (microtonalAdjustment * 8192.0 / 100.0); double note = ((octave+1)*12)+semitone; // This gives a MIDI value, 0 - 128 if (note > 127) note = 127; StringBuilder buddy = new StringBuilder(); buddy.append(":PitchWheel("); buddy.append((int)pitches); buddy.append(") "); buddy.append((int)note); buddy.append(qualifier); buddy.append(" :PitchWheel(8192)"); // Reset the pitch wheel. 8192 = original pitch wheel position return buddy.toString(); } }