/* * 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.theory; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import org.jfugue.pattern.NotesProducer; import org.jfugue.pattern.Pattern; import org.jfugue.pattern.PatternProducer; import org.jfugue.provider.NoteProviderFactory; import org.staccato.NoteSubparser; public class Intervals implements PatternProducer, NotesProducer { private static Map<Integer, Integer> wholeNumberDegreeToHalfsteps; private static Map<Integer, Integer> halfstepsToWholeNumberDegree; static { wholeNumberDegreeToHalfsteps = new HashMap<Integer, Integer>(); wholeNumberDegreeToHalfsteps.put(1, 0); wholeNumberDegreeToHalfsteps.put(2, 2); wholeNumberDegreeToHalfsteps.put(3, 4); wholeNumberDegreeToHalfsteps.put(4, 5); wholeNumberDegreeToHalfsteps.put(5, 7); wholeNumberDegreeToHalfsteps.put(6, 9); wholeNumberDegreeToHalfsteps.put(7, 11); wholeNumberDegreeToHalfsteps.put(8, 12); wholeNumberDegreeToHalfsteps.put(9, 14); wholeNumberDegreeToHalfsteps.put(10, 16); wholeNumberDegreeToHalfsteps.put(11, 17); wholeNumberDegreeToHalfsteps.put(12, 19); wholeNumberDegreeToHalfsteps.put(13, 21); wholeNumberDegreeToHalfsteps.put(14, 23); wholeNumberDegreeToHalfsteps.put(15, 24); halfstepsToWholeNumberDegree = new HashMap<Integer, Integer>(); halfstepsToWholeNumberDegree.put(0, 1); halfstepsToWholeNumberDegree.put(2, 2); halfstepsToWholeNumberDegree.put(4, 3); halfstepsToWholeNumberDegree.put(5, 4); halfstepsToWholeNumberDegree.put(7, 5); halfstepsToWholeNumberDegree.put(9, 6); halfstepsToWholeNumberDegree.put(11, 7); halfstepsToWholeNumberDegree.put(12, 8); halfstepsToWholeNumberDegree.put(14, 9); halfstepsToWholeNumberDegree.put(16, 10); halfstepsToWholeNumberDegree.put(17, 11); halfstepsToWholeNumberDegree.put(19, 12); halfstepsToWholeNumberDegree.put(21, 13); halfstepsToWholeNumberDegree.put(23, 14); halfstepsToWholeNumberDegree.put(24, 15); }; private String intervalPattern; private Note rootNote; private static java.util.regex.Pattern numberPattern = java.util.regex.Pattern.compile("\\d+"); private String[] splitEachSequence; private String[] splitAllSequence; public Intervals(String intervalPattern) { this.intervalPattern = intervalPattern; } public Intervals setRoot(String root) { return setRoot(NoteProviderFactory.getNoteProvider().createNote(root)); } public Intervals setRoot(Note root) { this.rootNote = root; return this; } @Override public org.jfugue.pattern.Pattern getPattern() { assert (rootNote != null); String[] intervals = intervalPattern.split(" "); int allSequenceCounter = 0; org.jfugue.pattern.Pattern pattern = new org.jfugue.pattern.Pattern(); for (String interval : intervals) { Note note = new Note((byte)(rootNote.getValue() + Intervals.getHalfsteps(interval))); if (splitEachSequence != null) { for (String add : splitEachSequence) { pattern.add(note.toString() + add); } } else if (splitAllSequence != null) { pattern.add(note.toString() + splitAllSequence[allSequenceCounter++]); if (allSequenceCounter == splitAllSequence.length) { allSequenceCounter = 0; } } else { pattern.add(note); } } return pattern; } @Override public List<Note> getNotes() { List<Note> noteList = new ArrayList<Note>(); Pattern pattern = getPattern(); for (String split : pattern.toString().split(" ")) { if (NoteSubparser.getInstance().matches(split)) { noteList.add(new Note(split)); } } return noteList; } public String getNthInterval(int n) { return intervalPattern.split(" ")[n]; } public int size() { return intervalPattern.split(" ").length; } public static int getHalfsteps(String interval) { return wholeNumberDegreeToHalfsteps.get(getNumberPortionOfInterval(interval)) + calculateHalfstepsFromFlatsAndSharps(interval); } public int[] toHalfstepArray() { String[] intervals = intervalPattern.split(" "); int[] halfSteps = new int[intervals.length]; for (int i=0; i < intervals.length; i++) { halfSteps[i] = Intervals.getHalfsteps(intervals[i]); } return halfSteps; } /** * Counts the number of halfsteps reflected by the * flats or sharps in a given interval. So, for "b3", * this would return -1. For "##5", this would return 2. */ private static int calculateHalfstepsFromFlatsAndSharps(String interval) { int numHalfsteps = 0; for (char ch : interval.toUpperCase().toCharArray()) { if (ch == 'B') { numHalfsteps -= 1; } else if (ch == '#') { numHalfsteps += 1; } } return numHalfsteps; } /** * Returns the number part of an interval token. * Interval tokens are typically like "1" or "3" but * could also be like "b3" or "#5". So, for example, * given either "3" or "b3", this method would return 3. * If there is no number in the given String (e.g., "#"), * which would probably be in error anyway, this method * returns 0. */ private static int getNumberPortionOfInterval(String interval) { Matcher m = numberPattern.matcher(interval); if (m.find()) { return Integer.parseInt(m.group()); } else { return 0; } } /** * Rotates an interval string by the given value. For * example, with an Interval like "1 3 5" and rotate(1), * this would return "3 5 1" (not "5 1 3"). */ public Intervals rotate(int n) { String[] intervals = intervalPattern.split(" "); n %= intervals.length; StringBuilder buddy = new StringBuilder(); for (int i=0; i < intervals.length-n; i++) { buddy.append(intervals[n+i]); buddy.append(" "); } for (int i=0; i < n; i++) { buddy.append(intervals[i]); buddy.append(" "); } this.intervalPattern = buddy.toString().trim(); return this; } public Intervals eachIntervalAs(String eachSequence) { this.splitEachSequence = eachSequence.split(" "); return this; } public Intervals allIntervalsAs(String allSequence) { this.splitAllSequence = allSequence.split(" "); return this; } public String toString() { return this.intervalPattern; } @Override public boolean equals(Object o) { if ((o == null) || (!(o instanceof Intervals))) return false; return (((Intervals)o).toString().equals(this.toString())); } @Override public int hashCode() { return this.toString().hashCode(); } public static Intervals createIntervalsFromNotes(Pattern pattern) { return createIntervalsFromNotes(pattern.toString()); } public static Intervals createIntervalsFromNotes(String noteString) { String[] noteStrings = noteString.split(" "); Note[] notes = new Note[noteStrings.length]; for (int i=0; i < noteStrings.length; i++) { notes[i] = NoteProviderFactory.getNoteProvider().createNote(noteStrings[i]); } return createIntervalsFromNotes(notes); } public static Intervals createIntervalsFromNotes(Note[] notes) { StringBuilder buddy = new StringBuilder(); buddy.append("1 "); for (int i=1; i < notes.length; i++) { int diff = notes[i].getValue()-notes[0].getValue(); if (!halfstepsToWholeNumberDegree.containsKey(diff)) { diff += 1; buddy.append("b"); } int wholeNumberDegree = halfstepsToWholeNumberDegree.get(diff); buddy.append(wholeNumberDegree); buddy.append(" "); } return new Intervals(buddy.toString().trim()); } }