/*
* 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.jfugue.pattern;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import org.jfugue.midi.MidiDictionary;
import org.staccato.IVLSubparser;
import org.staccato.NoteSubparser;
import org.staccato.TempoSubparser;
public class Pattern implements PatternProducer
{
protected StringBuilder patternSB;
private int explicitVoice = UNDECLARED_EXPLICIT;
private int explicitInstrument = UNDECLARED_EXPLICIT;
private int explicitTempo = UNDECLARED_EXPLICIT;
public Pattern() {
patternSB = new StringBuilder();
}
public Pattern(String string) {
this();
patternSB.append(string);
}
public Pattern(String... strings) {
this();
for (String string : strings) {
patternSB.append(string);
patternSB.append(" ");
}
}
public Pattern(PatternProducer... producers) {
this();
this.add(producers);
}
public Pattern add(PatternProducer... producers) {
for (PatternProducer producer : producers) {
this.add(producer.getPattern().toString());
}
return this;
}
public Pattern add(String string) {
if (patternSB.length() > 0) {
patternSB.append(" ");
}
patternSB.append(string);
return this;
}
public Pattern prepend(PatternProducer... producers) {
for (PatternProducer producer : producers) {
this.prepend(producer.getPattern().toString());
}
return this;
}
public Pattern prepend(String string) {
if (patternSB.length() > 0) {
patternSB.insert(0, " ");
}
patternSB.insert(0, string);
return this;
}
// This method necessarily digs into Staccato to get the VOICE indicator
public Pattern addTrack(int trackNumber, PatternProducer producer) {
patternSB.append(" ");
patternSB.append(IVLSubparser.VOICE);
patternSB.append(trackNumber);
patternSB.append(" ");
patternSB.append(producer);
return this;
}
public Pattern clear() {
patternSB.delete(0, patternSB.length());
return this;
}
public Pattern repeat(int n) {
Pattern p2 = new Pattern();
for (int i=0; i < n; i++) {
p2.add(this.patternSB.toString());
}
this.patternSB = p2.patternSB;
return this;
}
@Override
public Pattern getPattern() {
return this;
}
public String toString() {
StringBuilder b2 = new StringBuilder();
// Add the explicit tempo, if one has been provided
if (explicitTempo != UNDECLARED_EXPLICIT) {
b2.append(TempoSubparser.TEMPO);
b2.append(explicitTempo);
b2.append(" ");
}
// Add the explicit voice, if one has been provided
if (explicitVoice != UNDECLARED_EXPLICIT) {
b2.append(IVLSubparser.VOICE);
b2.append(explicitVoice);
b2.append(" ");
}
// Add the explicit voice, if one has been provided
if (explicitInstrument != UNDECLARED_EXPLICIT) {
b2.append(IVLSubparser.INSTRUMENT);
b2.append("[");
b2.append(MidiDictionary.INSTRUMENT_BYTE_TO_STRING.get((byte)explicitInstrument));
b2.append("] ");
}
// Now add the actual contents of the pattern!
b2.append(patternSB);
return b2.toString();
}
/*
* Explicit setters for tempo, voice, and instrument
*/
/**
* Provides a way to explicitly set the tempo on a Pattern directly
* through the pattern rather than by adding text to the contents
* of the Pattern.
*
* When Pattern.toString() is called, the a tempo will be prepended
* to the beginning of the pattern in the form of "Tx", where x is the
* tempo number.
*
* @return this pattern
*/
public Pattern setTempo(int explicitTempo) {
this.explicitTempo = explicitTempo;
return this;
}
/**
* Provides a way to explicitly set the tempo on a Pattern directly
* through the pattern rather than by adding text to the contents
* of the Pattern.
*
* When Pattern.toString() is called, the a tempo will be prepended
* to the beginning of the pattern in the form of "Tx", where x is the
* tempo number (even though this method takes a string as a parameter)
*
* @return this pattern
*/
public Pattern setTempo(String tempo) {
if (!MidiDictionary.TEMPO_STRING_TO_INT.containsKey(tempo.toUpperCase())) {
throw new RuntimeException("The tempo '"+tempo+"' is not recognized");
}
return setTempo(MidiDictionary.TEMPO_STRING_TO_INT.get(tempo.toUpperCase()));
}
/**
* Provides a way to explicitly set the instrument on a Pattern directly
* through the pattern rather than by adding text to the contents
* of the Pattern.
*
* When Pattern.toString() is called, the a voice will be prepended
* to the beginning of the pattern after any explicit tempo and before any
* explicit instrument in the form of "Vx", where x is the voice number
*
* @return this pattern
*/
public Pattern setVoice(int voice) {
this.explicitVoice = voice;
return this;
}
/**
* Provides a way to explicitly set the instrument on a Pattern directly
* through the pattern rather than by adding text to the contents
* of the Pattern.
*
* When Pattern.toString() is called, the a instrument will be prepended
* to the beginning of the pattern after any explicit voice in the form of
* "I[instrument-name]" (even though this method takes an integer as a parameter)
*
* @return this pattern
*/
public Pattern setInstrument(int instrument) {
this.explicitInstrument = instrument;
return this;
}
/**
* Provides a way to explicitly set the instrument on a Pattern directly
* through the pattern rather than by adding text to the contents
* of the Pattern.
*
* When Pattern.toString() is called, the a instrument will be prepended
* to the beginning of the pattern after any explicit voice in the form of
* "I[instrument-name]"
*
* @return this pattern
*/
public Pattern setInstrument(String instrument) {
if (!MidiDictionary.INSTRUMENT_STRING_TO_BYTE.containsKey(instrument.toUpperCase())) {
throw new RuntimeException("The instrument '"+instrument+"' is not recognized");
}
return setInstrument(MidiDictionary.INSTRUMENT_STRING_TO_BYTE.get(instrument.toUpperCase()));
}
/*
* Decorate each note
*/
/**
* Expects a parameter of "note decorators" - i.e., things that are added to
* the end of a note, such as duration or attack/decay settings; splits the given
* parameter on spaces and applies each decorator to each note as it is encountered
* in the current pattern.
*
* If there is one decorator in the parameter, this method will apply that same
* decorator to all note in the pattern.
*
* If there are more notes than decorators, a counter resets to 0 and the decorators
* starting from the first are applied to the future notes.
*
* Examples:
*
* new Pattern("A B C").addToEachNoteElement("q") --> "Aq Bq Cq"
* new Pattern("A B C").addToEachNoteElement("q i") --> "Aq Bi Cq" (rolls back to q for third note)
* new Pattern("A B C").addToEachNoteElement("q i s") --> "Aq Bi Cs"
* new Pattern("A B C").addToEachNoteElement("q i s w") --> "Aq Bi Cs" (same as "q i s")
*
* @return this pattern
*/
public Pattern addToEachNoteElement(String decoratorString) {
StringBuilder b2 = new StringBuilder();
int currentDecorator = 0;
String[] decorators = decoratorString.split(" ");
String[] elements = patternSB.toString().split(" ");
for (String element : elements) {
if (NoteSubparser.getInstance().matches(element)) {
b2.append(element);
b2.append(decorators[currentDecorator++ % decorators.length]);
} else {
b2.append(element);
}
b2.append(" ");
}
this.patternSB = new StringBuilder(b2.toString().trim());
return this;
}
public Pattern save(File file) throws IOException {
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
writer.write(this.toString());
writer.close();
return this;
}
public static Pattern load(File file) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(file));
Pattern pattern = new Pattern();
String line = null;
while ((line = reader.readLine()) != null) {
pattern.add(line);
}
reader.close();
return pattern;
}
private static final int UNDECLARED_EXPLICIT = -1;
}