/** * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2013 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.musicdroid.note.midi; import com.leff.midi.MidiFile; import com.leff.midi.MidiTrack; import com.leff.midi.event.ChannelEvent; import com.leff.midi.event.ProgramChange; import com.leff.midi.event.meta.Tempo; import com.leff.midi.event.meta.Text; import com.leff.midi.event.meta.TimeSignature; import org.catrobat.musicdroid.note.Instrument; import org.catrobat.musicdroid.note.NoteEvent; import org.catrobat.musicdroid.note.Project; import org.catrobat.musicdroid.note.Track; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class ProjectToMidiConverter { public static final String MUSICDROID_MIDI_FILE_IDENTIFIER = "Musicdroid Midi File"; private static final int MAX_CHANNEL = 16; private NoteEventToMidiEventConverter eventConverter; private ArrayList<Instrument> usedChannels; public ProjectToMidiConverter() { eventConverter = new NoteEventToMidiEventConverter(); usedChannels = new ArrayList<Instrument>(); } public void convertProjectAndWriteMidi(Project project, String path) throws IOException, MidiException { MidiFile midi = convertProject(project); File file = new File(path + File.separator + project.getName() + ".midi"); midi.writeToFile(file); } protected MidiFile convertProject(Project project) throws MidiException { ArrayList<MidiTrack> tracks = new ArrayList<MidiTrack>(); MidiTrack tempoTrack = createTempoTrackWithMetaInfo(project.getBeatsPerMinute()); tracks.add(tempoTrack); for (int i = 0; i < project.size(); i++) { Track track = project.getTrack(i); int channel = addInstrumentAndGetChannel(track.getInstrument()); MidiTrack noteTrack = createNoteTrack(track, channel); tracks.add(noteTrack); } return new MidiFile(MidiFile.DEFAULT_RESOLUTION, tracks); } protected int addInstrumentAndGetChannel(Instrument instrument) throws MidiException { if (usedChannels.contains(instrument)) { return usedChannels.indexOf(instrument) + 1; } else if (usedChannels.size() == MAX_CHANNEL) { throw new MidiException("You cannot have more than " + MAX_CHANNEL + " channels!"); } else { usedChannels.add(instrument); return usedChannels.indexOf(instrument) + 1; } } protected MidiTrack createTempoTrackWithMetaInfo(int beatsPerMinute) { MidiTrack tempoTrack = new MidiTrack(); Text text = new Text(0, 0, MUSICDROID_MIDI_FILE_IDENTIFIER); tempoTrack.insertEvent(text); Tempo tempo = new Tempo(); tempo.setBpm(beatsPerMinute); tempoTrack.insertEvent(tempo); TimeSignature timeSignature = new TimeSignature(); timeSignature.setTimeSignature(4, 4, TimeSignature.DEFAULT_METER, TimeSignature.DEFAULT_DIVISION); tempoTrack.insertEvent(timeSignature); return tempoTrack; } public MidiTrack createNoteTrack(Track track, int channel) throws MidiException { MidiTrack noteTrack = new MidiTrack(); ProgramChange program = new ProgramChange(0, channel, track.getInstrument().getProgram()); noteTrack.insertEvent(program); for (long tick : track.getSortedTicks()) { List<NoteEvent> noteEventList = track.getNoteEventsForTick(tick); for (NoteEvent noteEvent : noteEventList) { ChannelEvent channelEvent = eventConverter.convertNoteEvent(tick, noteEvent, channel); noteTrack.insertEvent(channelEvent); } } return noteTrack; } }