com.rockhoppertech.music.chord.ChordProgression.java Source code

Java tutorial

Introduction

Here is the source code for com.rockhoppertech.music.chord.ChordProgression.java

Source

package com.rockhoppertech.music.chord;

/*
 * #%L
 * Rocky Music Core
 * %%
 * Copyright (C) 1996 - 2014 Rockhopper Technologies
 * %%
 * 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.
 * #L%
 */

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang3.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.rockhoppertech.music.PitchFormat;
import com.rockhoppertech.music.midi.js.MIDITrack;
import com.rockhoppertech.music.scale.Scale;
import com.rockhoppertech.music.scale.ScaleFactory;

/**
 * A sequence of chords. Can use a format similar to fake books.
 * 
 * @author <a href="mailto:gene@rockhoppertech.com">Gene De Lisa</a>
 * 
 */
public class ChordProgression implements Iterable<Chord> {
    private static final Logger logger = LoggerFactory.getLogger(ChordFactory.class);

    /**
     * The data.
     */
    private List<Chord> chords = new ArrayList<Chord>();

    /**
     * 
     */
    private String name = "Untitled";

    /**
     * The default key.
     */
    private String key = "C";

    /**
     * The default scale.
     */
    private String scaleName = "Major";

    /**
     * The default beats per measure.
     */
    private int beatsPerMeasure = 4;

    public ChordProgression() {
    }

    public ChordProgression(final List<Chord> chords) {
        this.chords = Lists.newArrayList(chords);
        // this.chords = chords;
    }

    public ChordProgression(final List<Chord> chords, final String name) {
        this(chords);
        this.name = name;
    }

    /**
     * Append a chord to the progression.
     * 
     * @param chord the Chord to add.
     * @return this to allow cascading
     */
    public ChordProgression add(final Chord chord) {
        this.chords.add(chord);
        return this;
    }

    public static ChordProgression create(final File file) throws UnknownChordException {
        ChordProgression chordProgression = ChordProgressionParser.createFromFile(file);
        chordProgression.closeTimeline();
        return chordProgression;
    }

    /**
     * Input string can be either roman or std.
     * 
     * @param input A string with the chord changes.
     * @return ChordProgression
     * @throws UnknownChordException if the string contains an unregistered chord.
     */
    public static ChordProgression create(final String input) throws UnknownChordException {
        return ChordProgressionParser.create(input);
    }

    public static ChordProgression createFromNames(final String input) throws UnknownChordException {
        ChordProgression chordProgression = ChordProgressionParser.chordsFromStdNames(input);
        chordProgression.closeTimeline();
        return chordProgression;
    }

    /**
     * Specify roman numerals for chords. The default scale and key is C major.
     * 
     * @param input
     *            roman numerals
     * @return a progression based on the input
     */
    public static ChordProgression createFromRoman(final String input) {
        RomanChordParser.setDefaultScaleAndKey(ScaleFactory.getScaleByName("Major"), "C");
        ChordProgression chordProgression = ChordProgressionParser.chordsFromRoman(input);
        chordProgression.closeTimeline();
        return chordProgression;
    }

    public static ChordProgression createFromRoman(final String input, final String scaleName, final String key) {
        RomanChordParser.setDefaultScaleAndKey(ScaleFactory.getScaleByName(scaleName), key);
        ChordProgression chordProgression = ChordProgressionParser.chordsFromRoman(input);
        chordProgression.closeTimeline();
        return chordProgression;
    }

    public static String toRoman(final ChordProgression chordProgression, final String key, final Scale scale,
            final int beatsPerMeasure) {
        final StringBuilder sb = new StringBuilder();
        double beat = 1d;
        for (final Chord chord : chordProgression) {
            // String ps = PitchFormat.getPitchString(chord.getRoot());
            // String roman = RomanChordParser.pitchNameToRoman(scale, ps, key);
            final String roman = scale.getRoman(key, chord);

            // String sym = chord.getSymbol();
            if (beat > beatsPerMeasure) {
                beat = 1d;
                sb.append('|');
            }
            sb.append(roman).append(' ');

            // if (sym.equals("maj") || sym.equals("")) {
            // sb.append(roman).append(' ');
            // } else {
            // sb.append(roman).append(' ');
            // //sb.append(roman).append(chord.getSymbol()).append(' ');
            // }

            final double chordDur = chord.getDuration();
            logger.debug(String.format("beat %f chord dur %f", beat, chordDur));

            if (chordDur > 1d) {
                for (int i = 0; i < chordDur - 1; i++) {
                    sb.append("/ ");
                }
            }

            beat += chordDur;
        }
        return sb.toString().trim();
    }

    /**
     * Used by the transferhandler.
     * 
     * @param chordProgression the progression
     * @param beatsPerMeasure how many beats in the measure
     * @return a string
     */
    public static String asString(final ChordProgression chordProgression, final int beatsPerMeasure) {
        final StringBuilder sb = new StringBuilder();
        double beat = 1d;
        sb.append("key:").append(chordProgression.getKey()).append("\n");
        sb.append("beatsPerMeasure:").append(chordProgression.getBeatsPerMeasure()).append("\n");
        for (final Chord chord : chordProgression) {
            if (beat > beatsPerMeasure) {
                beat = 1d;
                sb.append('|');
            }
            sb.append(chord.getDisplayName()).append(' ');
            final double chordDur = chord.getDuration();
            if (ChordProgression.logger.isDebugEnabled()) {
                ChordProgression.logger.debug(String.format("beat %f chord dur %f", beat, chordDur));
            }
            if (chordDur > 1d) {
                for (int i = 0; i < chordDur - 1; i++) {
                    sb.append("/ ");
                }
            }
            beat += chordDur;
        }
        return sb.toString().trim();
    }

    public double closeTimeline() {
        return this.closeTimeline(0d);
    }

    /**
     * Removes beats between chords.
     * 
     * @param gap
     *            duration between chords
     * @return the total
     */
    public double closeTimeline(final double gap) {
        final int size = this.chords.size();
        final Chord n = this.chords.get(0);
        double s = n.getStartBeat();
        final double begin = s;
        double d = n.getDuration();
        double total = d - s;
        for (int i = 1; i < size; i++) {
            final Chord nn = this.chords.get(i);
            nn.setStartBeat(s + d + gap);
            s = nn.getStartBeat();
            d = nn.getDuration();
            total = (s + d) - begin;
        }
        return total;
    }

    public Chord get(final int index) {
        return this.chords.get(index);
    }

    public int getBeatsPerMeasure() {
        return this.beatsPerMeasure;
    }

    /**
     * Return the Chord that matches if the beat is between the Chord's start
     * and end.
     * 
     * @param beat
     *            a beat
     * @return a Chord or null if not chords at beat
     */
    public Chord getChordAtBeat(final double beat) {
        logger.debug("looking for  sb {}", beat);
        for (final Chord n : this) {
            final double s = n.getStartBeat();
            final double e = n.getEndBeat();
            logger.debug("checking sb {} of chord {}", s, n);
            Range<Double> range = Range.between(s, e - .0001);
            logger.debug("range {}", range);
            if (range.contains(beat)) {
                logger.debug("returning chord {}", n);
                return n;
            }
        }
        return null;
    }

    public String getKey() {
        return this.key;
    }

    public int getLength() {
        return this.chords.size();
    }

    public MIDITrack createMIDITrack() {
        if (this.chords != null) {
            final MIDITrack notelist = ChordProgressionParser.createMIDITrack(this.chords);
            return notelist;
        }
        return null;
    }

    public String getName() {
        return this.name;
    }

    public String getScaleName() {
        return this.scaleName;
    }

    public boolean insertAtIndex(final int index, final ChordProgression chordProgression) {
        return this.chords.addAll(index, chordProgression.chords);
    }

    public boolean isEmpty() {
        return this.chords.isEmpty();
    }

    @Override
    public Iterator<Chord> iterator() {
        return this.chords.iterator();
    }

    public void play() {
        final MIDITrack notelist = this.createMIDITrack();
        if (notelist != null) {
            notelist.play();
        }
    }

    /**
     * Removes the chord but leaves the timeline intact.
     * 
     * @param chord the chord to remove
     * @return boolean is it was removed
     * @see ChordProgression#closeTimeline()
     */
    public boolean remove(final Chord chord) {
        return this.chords.remove(chord);
    }

    public Chord remove(final int index) {
        return this.chords.remove(index);
    }

    public Chord set(final int index, final Chord chord) {
        return this.chords.set(index, chord);
    }

    public void setBeatsPerMeasure(final int beatsPerMeasure) {
        this.beatsPerMeasure = beatsPerMeasure;
    }

    public void setChords(final List<Chord> chords) {
        this.chords = chords;
    }

    public void setKey(final String key) {
        this.key = key;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public void setScaleName(final String scaleName) {
        this.scaleName = scaleName;
    }

    public void sortByStartBeat() {
        if (this.chords == null) {
            return;
        }
        final Comparator<Chord> c = new Comparator<Chord>() {
            @Override
            public int compare(final Chord o1, final Chord o2) {
                if (Math.abs(o1.getStartBeat() - o2.getStartBeat()) < .0000001) {
                    return 0;
                }
                if (o1.getStartBeat() < o2.getStartBeat()) {
                    return -1;
                }
                if (o1.getStartBeat() > o2.getStartBeat()) {
                    return 1;
                }
                return 0;
            }
        };
        Collections.sort(this.chords, c);
    }

    public String toRoman(final boolean omitSymbol) {
        final StringBuilder sb = new StringBuilder();
        double beat = 1d;
        final Scale scale = ScaleFactory.getScaleByName(this.scaleName);

        for (final Chord chord : this) {
            final String roman = scale.getRoman(this.key, chord, omitSymbol);
            if (beat > this.beatsPerMeasure) {
                beat = 1d;
                sb.append('|');
            }
            sb.append(roman).append(' ');

            // if (sym.equals("maj") || sym.equals("")) {
            // sb.append(roman).append(' ');
            // } else {
            // sb.append(roman).append(' ');
            // //sb.append(roman).append(chord.getSymbol()).append(' ');
            // }

            final double chordDur = chord.getDuration();
            if (ChordProgression.logger.isDebugEnabled()) {
                ChordProgression.logger.debug(String.format("beat %f chord dur %f", beat, chordDur));
            }
            if (chordDur > 1d) {
                for (int i = 0; i < chordDur - 1; i++) {
                    sb.append("/ ");
                }
            }
            beat += chordDur;
        }
        sb.append("|");
        return sb.toString().trim();
    }

    public String toHTML() {
        return toHTML(4);
    }

    public String toHTML(int measuresPerRow) {
        StringBuilder sb = new StringBuilder();
        String std = toStd(false, true);
        String[] a = std.split("[.]* \\|");
        // String[] a = std.split("[A-G][#sbf]?[m|maj]? |");
        int count = 0;
        sb.append("<html>\n");
        sb.append("<head>\n");
        sb.append(String.format("<title>%s</title>%n", this.getName()));
        sb.append("</head>\n");
        sb.append("<body>\n");
        sb.append(String.format("<p>%s</p>%n", this.getName()));
        sb.append(String.format("<p>%s %s</p>%n", this.getKey(), this.getScaleName()));
        sb.append(String.format("<p>%d</p>%n", this.getBeatsPerMeasure()));
        sb.append("<table border=\"1\" cellpadding=\"4\" summary=\"Chord Progression\">\n");
        sb.append("<tbody>\n");
        for (String s : a) {
            if (count == 0) {
                sb.append("<tr>");
            }

            sb.append("<td>");
            // the regexp eats the |
            sb.append(s.trim());
            // sb.append(s.trim()).append('|');
            sb.append("</td>");

            if (!(++count < measuresPerRow)) {
                sb.append("</tr>\n");
                count = 0;
            }
        }
        sb.append("</tr>");
        sb.append("\n</tbody></table>\n");
        sb.append("</body></html>");
        return sb.toString();
    }

    public String toRomanRows(int measuresPerRow) {
        String newline = System.getProperty("line.separator");
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("name:%s%s", this.getName(), newline));
        sb.append(String.format("key:%s %s%s", this.getKey(), this.getScaleName(), newline));
        sb.append(String.format("beatsPerMeasure:%d%s", this.getBeatsPerMeasure(), newline));

        String std = toRoman(false);
        String[] a = std.split("[.]* \\|");
        // String[] a = std.split("[A-G][#sbf]?[m|maj]? |");
        int count = 0;
        for (String s : a) {
            // the regexp eats the |
            sb.append(s.trim()).append('|');
            if (!(++count < measuresPerRow)) {
                count = 0;
                sb.append(newline);
            }
        }
        sb.append(newline);
        return sb.toString();
    }

    /**
     * Standard chord names but broken into the specified number of measures per
     * row.
     * 
     * @param measuresPerRow how many measures per row to print.
     * @return a string of the progression.
     */
    public String toStdRows(int measuresPerRow) {
        String newline = System.getProperty("line.separator");
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("name:%s%s", this.getName(), newline));
        sb.append(String.format("key:%s %s%s", this.getKey(), this.getScaleName(), newline));
        sb.append(String.format("beatsPerMeasure:%d%s", this.getBeatsPerMeasure(), newline));

        String std = toStd(false, true);
        String[] a = std.split("[.]* \\|");
        // String[] a = std.split("[A-G][#sbf]?[m|maj]? |");
        int count = 0;
        for (String s : a) {
            // the regexp eats the |
            sb.append(s.trim()).append('|');
            if (!(++count < measuresPerRow)) {
                count = 0;
                sb.append(newline);
            }
        }
        sb.append(newline);
        return sb.toString();
    }

    public String toStd() {
        return toStd(false, true);
    }

    public String toStd(final boolean omitSymbol) {
        return toStd(omitSymbol, true);
    }

    /**
     * The toString values for each chord for debugging.
     * 
     * @return s string
     */
    public String toChordStrings() {
        final StringBuilder sb = new StringBuilder();
        for (final Chord chord : this) {
            sb.append(chord.toString()).append('\n');
        }
        return sb.toString();
    }

    public String toStd(final boolean omitSymbol, final boolean useSlashes) {
        final StringBuilder sb = new StringBuilder();
        double beat = 1d;

        for (final Chord chord : this) {
            if (beat > this.beatsPerMeasure) {
                beat = 1d;
                sb.append("| ");
            }
            if (omitSymbol) {
                sb.append(PitchFormat.getPitchString(chord.getRoot())).append(' ');
            } else {
                sb.append(chord.getDisplayName()).append(' ');
            }
            final double chordDur = chord.getDuration();
            if (ChordProgression.logger.isDebugEnabled()) {
                ChordProgression.logger.debug(String.format("beat %f chord dur %f", beat, chordDur));
            }
            if (chordDur > 1d) {
                if (chordDur > this.beatsPerMeasure) {
                    for (int len = 0; len < chordDur; len += this.beatsPerMeasure) {
                        if (useSlashes) {
                            for (int i = 0; i < this.beatsPerMeasure - 1; i++) {
                                sb.append("/ ");
                            }
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug(String.format("len %d chordDur %f", len, chordDur));
                        }
                        if (len < chordDur - beatsPerMeasure) {
                            sb.append("| ");
                            sb.append(chord.getDisplayName()).append(' ');
                        }
                        beat = 1d;
                    }
                } else {
                    if (useSlashes) {
                        for (int i = 0; i < chordDur - 1; i++) {
                            sb.append("/ ");
                        }
                    }
                }
            }
            beat += chordDur;
        }
        sb.append("|");
        return sb.toString().trim();
    }

    @Override
    public String toString() {
        return this.toStd(false);
    }

    public double getEndBeat() {
        double endBeat = 1d;
        for (Chord chord : this.chords) {
            double d = chord.getEndBeat();
            logger.debug("end beat of chord {}", chord);
            logger.debug("end beat {}", d);
            if (d > endBeat) {
                endBeat = d;
            }
        }
        return endBeat;
        // Chord last = this.chords.get(chords.size() - 1);
        // return last.getStartBeat() + last.getDuration();
    }

    public ChordProgression append(ChordProgression chordProgression) {
        this.closeTimeline();
        chordProgression.closeTimeline();
        double startBeat = this.getEndBeat();

        // avoid a concurrent modification exception
        if (this == chordProgression) {
            List<Chord> list = new ArrayList<Chord>();
            list.addAll(this.chords);
            for (Chord c : chordProgression.chords) {
                Chord dupe = new Chord(c);
                dupe.setStartBeat(startBeat);
                list.add(dupe);
                startBeat += dupe.getDuration();
            }
            this.setChords(list);
            return this;
        }

        for (Chord c : chordProgression.chords) {
            Chord dupe = new Chord(c);
            dupe.setStartBeat(startBeat);
            this.add(dupe);
            startBeat += dupe.getDuration();
        }
        return this;
    }

}