/** * Turns a NoteToken into a Pitch by equating their representations and then enforcing the Key of * the Piece and the Accidentals encountered in the given meter * * @param workingNote, the NoteToken from which to make a Pitch * @return A Pitch that represents the given input NoteToken */ private Pitch constructNote(NoteToken workingNote) { Pitch workingPitch; int octave = 0; // Add the note initally if ('A' <= workingNote.getNote() && workingNote.getNote() <= 'G') { workingPitch = new Pitch(workingNote.getNote()); } else { octave++; workingPitch = new Pitch(Character.toUpperCase(workingNote.getNote())).transpose(Pitch.OCTAVE); } // Transpose by the octaves octave += workingNote.getOctave(); workingPitch = workingPitch.octaveTranspose(workingNote.getOctave()); // Collect accidentals, use them to modify the accidental list switch (workingNote.getNote()) { case 'A': case 'a': if (workingNote.getAccidental() == Integer.MIN_VALUE) { keyAccidental.put("A" + octave, 0); } else if (workingNote.getAccidental() == 0) { keyAccidental.put("A" + octave, -1 * keySignature.get('A')); } else { keyAccidental.put("A" + octave, -1 * keySignature.get('A') + workingNote.getAccidental()); } break; case 'B': case 'b': if (workingNote.getAccidental() == Integer.MIN_VALUE) { keyAccidental.put("B" + octave, 0); } else if (workingNote.getAccidental() == 0) { keyAccidental.put("B" + octave, -1 * keySignature.get('B')); } else { keyAccidental.put("B" + octave, -1 * keySignature.get('B') + workingNote.getAccidental()); } break; case 'C': case 'c': if (workingNote.getAccidental() == Integer.MIN_VALUE) { keyAccidental.put("C" + octave, 0); } else if (workingNote.getAccidental() == 0) { keyAccidental.put("C" + octave, -1 * keySignature.get('C')); } else { keyAccidental.put("C" + octave, -1 * keySignature.get('C') + workingNote.getAccidental()); } break; case 'D': case 'd': if (workingNote.getAccidental() == Integer.MIN_VALUE) { keyAccidental.put("D" + octave, 0); } else if (workingNote.getAccidental() == 0) { keyAccidental.put("D" + octave, -1 * keySignature.get('D')); } else { keyAccidental.put("D" + octave, -1 * keySignature.get('D') + workingNote.getAccidental()); } break; case 'E': case 'e': if (workingNote.getAccidental() == Integer.MIN_VALUE) { keyAccidental.put("E" + octave, 0); } else if (workingNote.getAccidental() == 0) { keyAccidental.put("E" + octave, -1 * keySignature.get('E')); } else { keyAccidental.put("E" + octave, -1 * keySignature.get('E') + workingNote.getAccidental()); } break; case 'F': case 'f': if (workingNote.getAccidental() == Integer.MIN_VALUE) { keyAccidental.put("F" + octave, 0); } else if (workingNote.getAccidental() == 0) { keyAccidental.put("F" + octave, -1 * keySignature.get('F')); } else { keyAccidental.put("F" + octave, -1 * keySignature.get('F') + workingNote.getAccidental()); } break; case 'G': case 'g': if (workingNote.getAccidental() == Integer.MIN_VALUE) { keyAccidental.put("G" + octave, 0); } else if (workingNote.getAccidental() == 0) { keyAccidental.put("G" + octave, -1 * keySignature.get('G')); } else { keyAccidental.put("G" + octave, -1 * keySignature.get('G') + workingNote.getAccidental()); } break; } // Now modify by the accidental and key signature in tandem switch (workingNote.getNote()) { case 'A': case 'a': workingPitch = workingPitch.accidentalTranspose(keyAccidental.get("A" + octave)); workingPitch = workingPitch.accidentalTranspose(keySignature.get('A')); break; case 'B': case 'b': workingPitch = workingPitch.accidentalTranspose(keyAccidental.get("B" + octave)); workingPitch = workingPitch.accidentalTranspose(keySignature.get('B')); break; case 'C': case 'c': workingPitch = workingPitch.accidentalTranspose(keyAccidental.get("C" + octave)); workingPitch = workingPitch.accidentalTranspose(keySignature.get('C')); break; case 'D': case 'd': workingPitch = workingPitch.accidentalTranspose(keyAccidental.get("D" + octave)); workingPitch = workingPitch.accidentalTranspose(keySignature.get('D')); break; case 'E': case 'e': workingPitch = workingPitch.accidentalTranspose(keyAccidental.get("E" + octave)); workingPitch = workingPitch.accidentalTranspose(keySignature.get('E')); break; case 'F': case 'f': workingPitch = workingPitch.accidentalTranspose(keyAccidental.get("F" + octave)); workingPitch = workingPitch.accidentalTranspose(keySignature.get('F')); break; case 'G': case 'g': workingPitch = workingPitch.accidentalTranspose(keyAccidental.get("G" + octave)); workingPitch = workingPitch.accidentalTranspose(keySignature.get('G')); break; } return workingPitch; }
/** * Transforms the Voices of the Piece into Pitches which are then subscribed according to timing * to an internal SequencePlayer, then plays that SequencePlayer to play the Piece out loud * * <p>Catches SequencePlayer Errors internally to avoid them being part of the ABCMusic's * responsibility * * <p>Note: because tempo must be converted to quarter notes, tempos that are not divisible by 4 * will have rounding errors that propagate into the music itself, its fairly negligible, but it * exists. * * <p>Note: Assumes that 1/16 of the default note length is the shortest note that would be * desired to play, any note shorter will play for 1/16 of a default note duration */ public void PlayMusic() { try { // Right now it converts bpm to be based on the number of 1/4 given how many L*Q notes exist // with the quantized time being int quartersPerMin = (getTempo() * getNoteLength()[0] * 4) / (getNoteLength()[1]); int ticksPerQuarter = (getNoteLength()[1] * 16) / (getNoteLength()[0] * 4); player = new SequencePlayer(quartersPerMin, ticksPerQuarter); // Figure out what the Key means, and keep it as a map to edit notes as they are added keySignature = getKeySignature(); // Initialize the accidentals as none, then use that as what gets edited through a meter resetKeyAccidental(); for (Iterator<Voice> i = VoicesList.iterator(); i.hasNext(); ) { int startTick = 0; for (Iterator<Bars> j = i.next().BarsList.iterator(); j.hasNext(); ) { for (Iterator<Meters> k = j.next().MetersList.iterator(); k.hasNext(); ) { for (Iterator<NoteToken> l = k.next().getElts().iterator(); l.hasNext(); ) { NoteToken workingNote = l.next(); if (workingNote.getType() == Tokens.Type.CHORD) { int noteTicks = 0; for (int m = 0; m < workingNote.getElts().length; m++) { Pitch newNote = constructNote(workingNote.getElts()[m]); // Length of a note in terms of quarters double noteLength = ((double) workingNote.getElts()[m].getLength()[0] / (double) workingNote.getElts()[m].getLength()[1]) * ((double) (getNoteLength()[0] * 4) / ((double) getNoteLength()[1])); // Convert the quarters to valid ticks noteTicks = (int) (noteLength * ticksPerQuarter); player.addNote(newNote.toMidiNote(), startTick, noteTicks); } startTick += noteTicks; } else if (workingNote.getType() == Tokens.Type.REST) { // Don't need to make rests // Length of a note in terms of quarters double noteLength = ((double) workingNote.getLength()[0] / (double) workingNote.getLength()[1]) * ((double) (getNoteLength()[0] * 4) / ((double) getNoteLength()[1])); // Convert the quarters to valid ticks // Add ticks for the rests skipping time int noteTicks = (int) (noteLength * ticksPerQuarter); startTick += noteTicks; } else { Pitch newNote = constructNote(workingNote); // Length of a note in terms of quarters double noteLength = ((double) workingNote.getLength()[0] / (double) workingNote.getLength()[1]) * ((double) (getNoteLength()[0] * 4) / ((double) getNoteLength()[1])); // Convert the quarters to valid ticks int noteTicks = (int) (noteLength * ticksPerQuarter); player.addNote(newNote.toMidiNote(), startTick, noteTicks); startTick += noteTicks; } } // Reset Accidentals at the end of the Meter resetKeyAccidental(); } } } // Play the finished SequencePlayer with all Voice there-in player.play(); // Catch undesirable errors } catch (MidiUnavailableException e) { e.printStackTrace(); } catch (InvalidMidiDataException e) { e.printStackTrace(); } }
// Transforms a Song in to language that can be fed to the MIDI Sequencer public void play() throws MidiUnavailableException, InvalidMidiDataException { // LCM calculations that ultimately give us how many ticks per beat we should have Fraction lcmCalc = defaultNoteLength; int lcm = 0; for (String voiceName : this.musicForVoiceName.keySet()) { for (Music m : this.musicForVoiceName.get(voiceName)) { lcmCalc = new Fraction( 1, Fraction.LCM(m.getDuration().getDenominator(), lcmCalc.getDenominator())); } if (lcmCalc.getDenominator() > lcm) { lcm = lcmCalc.getDenominator(); } } LyricListener listener = new LyricListener() { public void processLyricEvent(String text) { System.out.println(text); } }; // Initializes a new SequencePlayer that will make our notes audible SequencePlayer seqPlayer = new SequencePlayer(this.beatsPerMinute, lcm, listener); int startTick = 0; int duration; // Considers every Voice in our Song for (String voiceName : this.musicForVoiceName.keySet()) { String lyricsPrefix = ""; if (!voiceName.equals("THE_DEFAULT_VOICE")) lyricsPrefix = voiceName; startTick = 0; // Considers every Music element in our Voice for (Music m : this.musicForVoiceName.get(voiceName)) { duration = (int) (m.getDuration().toDouble() * defaultNoteLength.toDouble() / tempoBeat.toDouble() * lcm); if (m instanceof Note) { Note mNote = (Note) m; Pitch pitch = new Pitch(mNote.getNote().toString().charAt(0)) .transpose(mNote.getAccidental().getSemitoneOffset() + 12 * mNote.getOctave()); if (mNote.getSyllable() != null && !mNote.getSyllable().equals("")) { seqPlayer.addLyricEvent(lyricsPrefix + mNote.getSyllable(), startTick); } seqPlayer.addNote(pitch.toMidiNote(), startTick, duration); startTick += duration; } else if (m instanceof Chord) { Chord mChord = (Chord) m; if (mChord.getSyllable() != null && !mChord.getSyllable().equals("")) { seqPlayer.addLyricEvent(lyricsPrefix + mChord.getSyllable(), startTick); } for (Note n : mChord.getNotes()) { Pitch pitch = new Pitch(n.getNote().toString().charAt(0)) .transpose(n.getAccidental().getSemitoneOffset() + 12 * n.getOctave()); seqPlayer.addNote(pitch.toMidiNote(), startTick, duration); } startTick += duration; } else if (m instanceof Tuplet) { Tuplet mTuplet = (Tuplet) m; int tupleNoteDur = duration; tupleNoteDur = getTupleNoteDur(mTuplet.getType(), tupleNoteDur); for (Music tupletElem : mTuplet.getNotes()) { if (tupletElem instanceof Note) { Pitch pitch1 = null; Note n = (Note) tupletElem; pitch1 = new Pitch(n.getNote().toString().charAt(0)) .transpose(n.getAccidental().getSemitoneOffset() + 12 * n.getOctave()); if (n.getSyllable() != null && !n.getSyllable().equals("")) { seqPlayer.addLyricEvent(lyricsPrefix + n.getSyllable(), startTick); } seqPlayer.addNote(pitch1.toMidiNote(), startTick, tupleNoteDur); startTick += tupleNoteDur; } else if (tupletElem instanceof Chord) { Chord c = (Chord) tupletElem; if (c.getSyllable() != null && !c.getSyllable().equals("")) { seqPlayer.addLyricEvent(lyricsPrefix + c.getSyllable(), startTick); } for (Note note : c.getNotes()) { Pitch pitch2 = null; pitch2 = new Pitch(note.getNote().toString().charAt(0)) .transpose( note.getAccidental().getSemitoneOffset() + 12 * note.getOctave()); seqPlayer.addNote(pitch2.toMidiNote(), startTick, tupleNoteDur); } startTick += tupleNoteDur; } else if (tupletElem instanceof Rest) { startTick += tupleNoteDur; } else { throw new RuntimeException( "You cannot build a tuple out of anything but a Note, Chord, or Rest"); } } } else if (m instanceof Rest) { startTick += duration; } } } seqPlayer.play(); }