/* * Created on 12.3.2007 * * Copyright (c) 2007 Karl Helgason * * http://www.frinika.com * * This file is part of Frinika. * * Frinika is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * Frinika 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with Frinika; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.frinika.mod; import java.awt.Component; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.TreeSet; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.MidiUnavailableException; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.filechooser.FileFilter; import rasmus.midi.provider.RasmusSynthesizer; import com.frinika.project.ProjectContainer; import com.frinika.project.gui.ProjectFrame; import com.frinika.sequencer.gui.mixer.SynthWrapper; import com.frinika.sequencer.midi.message.TempoMessage; import com.frinika.sequencer.model.ControllerEvent; import com.frinika.sequencer.model.MetaEvent; import com.frinika.sequencer.model.MidiLane; import com.frinika.sequencer.model.MidiPart; import com.frinika.sequencer.model.NoteEvent; import com.frinika.sequencer.model.Part; import com.frinika.sequencer.model.PitchBendEvent; import com.frinika.sequencer.model.ProgramChangeEvent; import com.vwp.sound.mod.modplay.loader.InvalidFormatException; import com.vwp.sound.mod.modplay.loader.ModuleLoader; import com.vwp.sound.mod.modplay.module.Instrument; import com.vwp.sound.mod.modplay.module.Module; import com.vwp.sound.mod.modplay.module.Pattern; import com.vwp.sound.mod.modplay.module.Sample; import com.vwp.sound.mod.modplay.module.Track; import com.vwp.sound.mod.modplay.player.Mixer; import com.vwp.sound.mod.modplay.player.ModuleState; import com.vwp.sound.mod.modplay.player.PlayerException; import com.vwp.sound.mod.modplay.player.TrackState; import static com.frinika.localization.CurrentLocale.getMessage; public class MODImporter { private static class ModulesFileFilter extends FileFilter { public boolean accept(File f) { if(f.isDirectory()) return true; if(!f.isFile()) return false; if(f.getName().toLowerCase().endsWith(".mod")) return true; if(f.getName().toLowerCase().endsWith(".xm")) return true; if(f.getName().toLowerCase().endsWith(".s3m")) return true; if(f.getName().toLowerCase().endsWith(".stm")) return true; if(f.getName().toLowerCase().endsWith(".it")) return true; if(f.getName().toLowerCase().endsWith(".zip")) return true; return false; } public String getDescription() { return "Module Files (*.mod,*.xm,*.s3m,*.stm,*.it,*.zip)"; } } public static void load(Component parent) { try { JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle(getMessage("project.menu.import_module")); chooser.setFileFilter(new ModulesFileFilter()); if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { File newFile = chooser.getSelectedFile(); if(newFile.isDirectory()) return; if(!newFile.isFile()) return; ProjectContainer project = new ProjectContainer(); load(newFile, project); ProjectFrame frame = new ProjectFrame(project); } ; } catch (Exception ex) { JOptionPane.showMessageDialog(parent, ex.toString()); ex.printStackTrace(); } } class XNoteEvent { long tick = 0; MidiPart part; int note = 0; int instrument = 0; double volume = 1.0; double pitch = 1.0; double pan = 0.5; } class XNoteEvents { ArrayList<XNoteEvent> events = new ArrayList<XNoteEvent>(); int track; boolean note_active = false; public void processEvent(int note, int instrument, double pitch, double volume, double pan) { if(note == -1) // Note End { commit(); return; } if(note != -2) // New Note { commit(); note_active = true; } if(!note_active) return; XNoteEvent event = new XNoteEvent(); event.tick = tickpos; event.part = getPart(track); event.note = note; event.instrument = instrument; event.pitch = pitch; event.volume = volume; event.pan = pan; events.add(event); } public void commit() { if(events.size() == 0) return; XNoteEvent firstevent = events.get(0); Iterator<XNoteEvent> iter = events.iterator(); double maxvol = firstevent.volume;; while (iter.hasNext()) { XNoteEvent event = iter.next(); if(event.volume > maxvol) maxvol = event.volume; } if(maxvol == 0) // Volume is always 0, skip this events { events.clear(); return; } // Set instrument first !!! if(firstevent.instrument >= 0) if(selected_instrument[track] != firstevent.instrument) { selected_instrument[track] = firstevent.instrument; int program = firstevent.instrument; if(program < 0) program = 0; if(program > 127) program = 127; ProgramChangeEvent programEvent = new ProgramChangeEvent(firstevent.part, firstevent.tick, program, 0, 0); programEvent.setProgram(program,0,0); firstevent.part.add(programEvent); } // Find out last tick pos, by looking at volume long lasttick = tickpos; for (int i = events.size() - 1; i >= 0 ; i--) { if(events.get(i).volume == 0) { lasttick = events.get(i).tick; } else { break; } } int pitchRange = 2; // Check for pitch range need iter = events.iterator(); while (iter.hasNext()) { XNoteEvent event = iter.next(); if(event.tick >= lasttick) break; double set_pitch = event.pitch; double pitchN = (Math.log(set_pitch)/Math.log(2))*12.0; if(Math.abs(pitchN) > 2) { int new_range = (int)(Math.abs(pitchN)+1); if(new_range > pitchRange) pitchRange = new_range; } } // Update pitch range if needed if(pitchRange != control_pitch_range[track]) { control_pitch_range[track] = pitchRange; if(pitchRange > 127) pitchRange = 127; // Select RPN : Pitch Bend Range firstevent.part.add(new ControllerEvent(firstevent.part, firstevent.tick, 0x65, 0)); firstevent.part.add(new ControllerEvent(firstevent.part, firstevent.tick, 0x64, 0)); // Data Entry coarse firstevent.part.add(new ControllerEvent(firstevent.part, firstevent.tick, 0x06, pitchRange)); firstevent.part.add(new ControllerEvent(firstevent.part, firstevent.tick, 0x26, pitchRange)); } // Add Control events iter = events.iterator(); while (iter.hasNext()) { XNoteEvent event = iter.next(); if(event.tick >= lasttick) break; double set_vol = event.volume / maxvol; double set_pitch = event.pitch; double set_pan = event.pan; if(set_vol != control_vol[track]) { control_vol[track] = set_vol; int val = (int)(127.0 * set_vol); if(val > 127) val = 127; if(val < 0) val = 0; event.part.add(new ControllerEvent(event.part, event.tick, 7, val)); } if(set_pan != control_pan[track]) { control_pan[track] = set_pan; int val = (int)(127.0 * set_pan); if(val > 127) val = 127; if(val < 0) val = 0; event.part.add(new ControllerEvent(event.part, event.tick, 10, val)); } if(set_pitch != control_pitch[track]) { control_pitch[track] = set_pitch; // Convert pitch to log double pitchN = (Math.log(set_pitch)/Math.log(2))*12.0; /* int pitchRange = 2; if(Math.abs(pitchN) > 2) pitchRange = (int)(Math.abs(pitchN)+1); if(pitchRange != control_pitch_range[track]) { control_pitch_range[track] = pitchRange; if(pitchRange > 127) pitchRange = 127; // Select RPN : Pitch Bend Range event.part.add(new ControllerEvent(event.part, event.tick, 0x65, 0)); event.part.add(new ControllerEvent(event.part, event.tick, 0x64, 0)); // Data Entry coarse event.part.add(new ControllerEvent(event.part, event.tick, 0x06, pitchRange)); } */ if(pitchN > pitchRange) pitchN = pitchRange; if(pitchN < -pitchRange) pitchN = -pitchRange; int val = (int)((pitchN + pitchRange)* (8192.0 / pitchRange)); if(val > 16256) val = 16256; if(val < 0) val = 0; event.part.add(new PitchBendEvent(event.part, event.tick, val)); } } // Add Note Event int vel = (int)(maxvol * 127.0); if(vel > 127) vel = 127; if(vel < 0) vel = 0; // public NoteEvent(MidiPart part, long startTick, int note, int velocity, int channel, long duration) firstevent.part.add(new NoteEvent(firstevent.part, firstevent.tick, firstevent.note, vel, 0, lasttick - firstevent.tick)); events.clear(); note_active = false; } } Module module; MidiLane[] lanes; MidiPart[] parts; XNoteEvents[] events; public MidiPart getPart(int track) { if(parts[track] != null) return parts[track]; parts[track] = new MidiPart(lanes[track]); parts[track].setStartTick(lastPatternSeperator); return parts[track]; } double[] control_pan; double[] control_vol; double[] control_pitch; int[] control_pitch_range; int[] selected_instrument; double current_tempo; double MIN_PITCH_RANGE = 2; long lastPatternSeperator = -1; long tickpos = 0; long tickpos_major = 0; long tickstep; // ticks per divition(in modules) long part_startpos = 0; boolean patternActive = false; private void endPattern() { for (int c = 0; c < lanes.length; c++) { if(parts[c] != null) { parts[c].setEndTick(tickpos); parts[c] = null; } } patternActive = false; } private void startPattern() { if(patternActive) if(lastPatternSeperator == tickpos) return; endPattern(); part_startpos = tickpos; for (int c = 0; c < lanes.length; c++) { parts[c] = null; } patternActive = true; lastPatternSeperator = tickpos; } public void processEvent(int track, int note, int instrument, double pitch, double volume, double pan) { events[track].processEvent(note,instrument,pitch,volume,pan); } public void finish() { for (int i = 0; i < events.length; i++) { events[i].commit(); } // Remove empty parts for (int i = 0; i < lanes.length; i++) { Part[] parts = new Part[lanes[i].getParts().size()]; lanes[i].getParts().toArray(parts); for (int j = 0; j < parts.length; j++) { if(parts[j] instanceof MidiPart) { MidiPart mpart = (MidiPart)parts[j]; if(mpart.getMultiEvents().isEmpty()) { mpart.removeFromModel(); } } } } // Remove lanes not used for (int i = 0; i < lanes.length; i++) { if(lanes[i].getParts().size() == 0) lanes[i].removeFromModel(); } } int trackcount; ProjectContainer project; private MODImporter(File file, ProjectContainer project) throws InvalidFormatException, IOException { this.project = project; tickstep = project.getSequence().getResolution()/4; ModuleLoader modloader = ModuleLoader.getModuleLoader(file); module = modloader.getModule(); // Count how many tracks are really used trackcount = 0; for (int i = 0; i < module.getNumberOfPositions(); i++) { Pattern pattern = module.getPatternAtPos(i); for (int j = 0; j < pattern.getTrackCount(); j++) { Track track = pattern.getTrack(j); for (int k = 0; k < pattern.getDivisions(); k++) { if(track.getNote(k) > 0) { if(trackcount < (j + 1)) trackcount = j + 1; } } } } lanes = new MidiLane[trackcount]; parts = new MidiPart[trackcount]; events = new XNoteEvents[trackcount]; control_pan = new double[trackcount]; control_vol = new double[trackcount]; control_pitch = new double[trackcount]; control_pitch_range = new int[trackcount]; selected_instrument = new int[trackcount]; StringBuffer buffer = new StringBuffer(); buffer.append("tostereo <- function(input)\n"); buffer.append("{ \n"); buffer.append(" <- channelmux(input, input); \n"); buffer.append("}\n"); buffer.append("\n"); Instrument[] instruments = module.getInstruments(); for (int i = 0; i < instruments.length; i++) { Instrument ins = instruments[i]; if(ins.getNumberOfSamples() == 1) { Sample sample = ins.getSampleByNum(0); File tempfile = getSampleFile(sample); if(tempfile != null) { String sampleid = "sample_" + i ; if(sample.getLoopType() != 0) buffer.append(sampleid + " <- sample('" + tempfile.getPath() + "', loopstart=" + sample.getLoopStart()*2 + ", loopend=" + (sample.getLoopStart() + sample.getLoopLength())*2 + ");\n"); else buffer.append(sampleid + " <- sample('" + tempfile.getPath() + "');\n"); buffer.append("instruments <- instrument(" + i + ",0,'" + ins.getName().replace('\'', '"')+ "')\n"); buffer.append(" <- function(note, velocity, pitch = 1, gate)\n"); buffer.append("{\n"); float rate = (float)sample.getUnits().note2rate(sample.getFineTune() + sample.getRelativeNote()); buffer.append(" rate <- " + rate + " * pow(2, note / 12) * pitch; \n"); buffer.append(" <- tostereo() <- gain(velocity*adsr(gate,0.01,0.01,1,0.01)) <- resamplei(rate/srate()) <- " + sampleid + "; \n"); buffer.append("} \n"); buffer.append("\n"); } } if(ins.getNumberOfSamples() > 1) { HashMap<Sample, String> sample_table = new HashMap<Sample, String>(); for (int j = 0; j < ins.getNumberOfSamples(); j++) { Sample sample = ins.getSampleByNum(0); File tempfile = getSampleFile(sample); if(tempfile != null) { String sampleid = "sample_" + j + "_" + i ; if(sample.getLoopType() != 0) buffer.append(sampleid + " <- sample('" + tempfile.getPath() + "', loopstart=" + sample.getLoopStart()*2 + ", loopend=" + (sample.getLoopStart() + sample.getLoopLength())*2 + ");\n"); else buffer.append(sampleid + " <- sample('" + tempfile.getPath() + "');\n"); buffer.append("instrument_" + j + "_" + i + " <- function(note, velocity, pitch = 1, gate)\n"); buffer.append("{\n"); float rate = (float)sample.getUnits().note2rate(sample.getFineTune() + sample.getRelativeNote()); buffer.append(" rate <- " + rate + " * pow(2, note / 12) * pitch; \n"); buffer.append(" <- tostereo() <- gain(velocity*adsr(gate,0.01,0.01,1,0.01)) <- resamplei(rate/srate()) <- " + sampleid + "; \n"); buffer.append("} \n"); buffer.append("\n"); sample_table.put(sample, "instrument_" + j + "_" + i ); } } for (int j = 0; j < 128; j++) { Sample sample = ins.getSampleByNote(j); int firstNote = j; if(sample != null) { for (; j < 128; j++) { if(ins.getSampleByNote(j + 1) != sample) { int lastNote = j - 1; String s = sample_table.get(sample); if(s != null) buffer.append("instrument_" + i + " <- registerVoice(" + firstNote + "," + lastNote + ") <- " + s + ";\n"); break; } } } } buffer.append("instruments <- instrument(" + i + ",0,'" + ins.getName().replace('\'', '"')+ "') <- instrument_" + i + ";\n"); } } SynthWrapper synthwrap = null; for (int i = 0; i < lanes.length; i++) { if(i % 16 == 0) { RasmusSynthesizer midiDevice = new RasmusSynthesizer(); //midiDevice.open(); synthwrap = new SynthWrapper(project, midiDevice); try { project.addMidiOutDevice(synthwrap); } catch (MidiUnavailableException e) { e.printStackTrace(); } midiDevice.setScript(buffer.toString()); } lanes[i] = project.createMidiLane(); lanes[i].setName("ch"+ (i+1)); lanes[i].getTrack().setMidiChannel(i % 16); lanes[i].getTrack().setMidiDevice(synthwrap); events[i] = new XNoteEvents(); events[i].track = i; control_pan[i] = -1; // Not set control_vol[i] = -1; control_pitch[i] = 1; control_pitch_range[i] = -1; // Pitch range (not set), default is 2 selected_instrument[i] = -1; // No instrument selected; } } private class ShortInputStream extends InputStream { short[] data; int lastindex; public ShortInputStream(short[] data) { this.data = data; lastindex = (data.length * 2) - 1; } int index = -1; public int read() throws IOException { if(index == lastindex) return -1; index++; short val = data[index >> 1]; if((index & 1) == 0) return (val >>> 0 ) & 0xFF ; else return (val >>> 8 ) & 0xFF; } } HashMap<Sample, File> samplestempfiles = new HashMap<Sample, File>(); private File getSampleFile(Sample sample) { if(sample == null) return null; if(sample.getData() == null) return null; if(sample.getData().length == 0) return null; File file = samplestempfiles.get(sample); if(file != null) return file; try { file = File.createTempFile("sample", ".wav"); } catch (IOException e) { e.printStackTrace(); return null; } short[] data = sample.getData(); ShortInputStream is = new ShortInputStream(data); float rate = (float)sample.getUnits().note2rate(36 + sample.getFineTune() + sample.getRelativeNote()); AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, 1, 2, rate*2, false); AudioInputStream audio_inputstream = new AudioInputStream(is, format, data.length * 2); try { AudioSystem.write(audio_inputstream, AudioFileFormat.Type.WAVE, file); } catch (IOException e) { e.printStackTrace(); if(file.exists()) file.delete(); return null; } file.deleteOnExit(); // Temp file should be deleted on frinika exit!!! samplestempfiles.put(sample, file); return file; } static final boolean DEBUG_PRINT_PATTERN_WHILE_LOADING = false; private void load() { //hardwired.xm,moscow.mod,odyssey1.mod /* System.out.println(); System.out.println("Instruments"); System.out.println("==========="); Instrument[] instruments = module.getInstruments(); for (int i = 0; i < instruments.length; i++) { Instrument ins = instruments[i]; System.out.println("name: " + ins.getName()); System.out.println("samples: " + ins.getNumberOfSamples()); System.out.println(); System.out.println("sample.length: " + ins.getSampleByNum(0).getLength()); System.out.println("sample.note: " + ins.getSampleByNum(0).getRelativeNote()); System.out.println("sample.tune: " + ins.getSampleByNum(0).getFineTune()); } System.out.println(); System.out.println("Orderlist"); System.out.println("========="); for (int i = 0; i < module.getNumberOfPositions(); i++) { System.out.println("pattern" + module.getPatternIndexAtPos(i)); } System.out.println(); System.out.println("Pattern0"); System.out.println("========"); Pattern pattern = module.getPattern(0); System.out.println("tracks: " + pattern.getTrackCount()); for (int i = 0; i < pattern.getDivisions(); i++) { System.out.print(i + "# "); for (int j = 0; j < pattern.getTrackCount(); j++) { System.out.print(pattern.getTrack(j).getInfo(i)+ " "); //System.out.print(pattern.getTrack(j).getInstrumentNumber(i)+ " "); } System.out.println(); } */ FRMixer mixer = new FRMixer(trackcount); ModuleState ms = new ModuleState(module, mixer); startPattern(); TrackState[] ts = null; // trackStates is private variable, so we just cheat a little // to access it Field field; try { field = ms.getClass().getDeclaredField("trackStates"); field.setAccessible(true); ts = (TrackState[])field.get(ms); } catch (Exception e1) { e1.printStackTrace(); } try { System.out.println(ms.getPosition() + ":" + ms.getDivision() + ":" + ms.getTick() + " first"); boolean engage = false; boolean ok = true; int last_position = ms.getPosition(); TreeSet<String> loop_detection = new TreeSet<String>(); String pos_id = ms.getPosition() + ":" + ms.getDivision(); loop_detection.add(pos_id); int beat = 0; while(ok) { int tick = ms.getTick(); if(last_position != ms.getPosition()) { System.out.println(); System.out.println(); startPattern(); } last_position = ms.getPosition(); ok = ms.play(); //if(tick == 0) { // In midi , let 4 divison be a beat, where one divison is a quarter note // Let's calculate BPM from that, lastplaytime is in msec // beat/ms ms * 1000 = sec // double bpm = 60000.0 / (mixer.lastplaytime*ms.getTicksInDivision()*4.0); if(tickpos == 0) { project.setTempoInBPM((float)bpm); current_tempo = bpm; } else { if(Math.abs(bpm - current_tempo) > 0.0001) { try { MetaEvent event = new MetaEvent(getPart(0), tickpos); event.setMessage(new TempoMessage((int)bpm)); getPart(0).add(event); } catch (InvalidMidiDataException e) { e.printStackTrace(); } current_tempo = bpm; } } //System.out.print((mixer.lastplaytime*ms.getTicksInDivision()) + " "); if(DEBUG_PRINT_PATTERN_WHILE_LOADING) if(tick == 0) System.out.print((((int)bpm)+ "bpm " + (beat/4.0) + " ").substring(0, 16)); if(DEBUG_PRINT_PATTERN_WHILE_LOADING) if(tick == 0) System.out.print((ms.getPosition() + ":" + ms.getDivision() + ":" + tick+ " ").substring(0, 10)); if(DEBUG_PRINT_PATTERN_WHILE_LOADING) if(tick == 0) System.out.print(" "); for (int i = 0; i < mixer.tracks.length; i++) { double baserate = 0; Sample sample = ts[i].getSample(); if(sample != null) if(ts[i].getNote() != Instrument.NO_NOTE) { baserate = sample.getUnits().note2rate(ts[i].getNote() + sample.getFineTune() + sample.getRelativeNote()); } if(baserate == 0) { processEvent(i, -1,-1,-1,-1,-1); // Note end / No active note if(DEBUG_PRINT_PATTERN_WHILE_LOADING) if(tick == 0) System.out.print((" ").substring(0, 10)); } else { if(!mixer.tracks[i].newNoteEvent) { if(DEBUG_PRINT_PATTERN_WHILE_LOADING) if(tick == 0) System.out.print((" -- -" + ("- ").substring(0, 2) + "" + (" p"+mixer.tracks[i].rate/baserate).substring(0,5) + (" v"+mixer.tracks[i].volume).substring(0,5) +" ").substring(0, 18)); processEvent(i, -2,-2,mixer.tracks[i].rate/baserate, mixer.tracks[i].volume,mixer.tracks[i].panning); } else { if(DEBUG_PRINT_PATTERN_WHILE_LOADING) if(tick == 0) System.out.print((" " + ts[i].getNote() + ":i" + (ts[i].getInstrument()+" ").substring(0, 2) + "" + (" p"+mixer.tracks[i].rate/baserate).substring(0,5) + (" v"+mixer.tracks[i].volume).substring(0,5) +" ").substring(0, 18)); processEvent(i, ts[i].getNote(),ts[i].getInstrument(),mixer.tracks[i].rate/baserate, mixer.tracks[i].volume,mixer.tracks[i].panning); } } } if(DEBUG_PRINT_PATTERN_WHILE_LOADING) if(tick == 0) System.out.println(); } if(tick == ms.getTicksInDivision()-1) { beat += 1; tickpos_major += tickstep; tickpos = tickpos_major; } else { tickpos += tickstep / ms.getTicksInDivision(); } if(ms.getTick() == 0) { pos_id = ms.getPosition() + ":" + ms.getDivision(); if(loop_detection.contains(pos_id)) { System.out.println("LOOP detected, break"); break; } loop_detection.add(pos_id); } /* if(!engage) { if(first_position != ms.getPosition()) { engage = true; } } else { if(first_position == ms.getPosition()) { System.out.println("We are looping"); break; } } */ } } catch (PlayerException e) { System.out.println("error in player"); e.printStackTrace(); } endPattern(); finish(); } public static void load(File file, ProjectContainer project) throws InvalidFormatException, IOException { new MODImporter(file, project).load(); project.validate(); } /* public static void load(File file, ProjectContainer project) throws InvalidFormatException, IOException { //Pattern pattern = module.getPattern(0); //System.out.println("tracks: " + pattern.getTrackCount()); NoteEvent[] active_noteevents = new NoteEvent[lanes.length]; long tickpos = 0; for (int p = 0; p < module.getNumberOfPositions(); p++) { Pattern pattern = module.getPatternAtPos(p); // Scan for end of pattern mark int pattlen = pattern.getDivisions(); body: for (int r = 0; r < pattern.getDivisions(); r++) { System.out.print(r + "# "); for (int c = 0; c < pattern.getTrackCount(); c++) { System.out.print(pattern.getTrack(c).getInfo(r)+ " "); int x_count = pattern.getTrack(c).getNumberOfEffects(r); for (int k = 0; k < x_count; k++) { if(pattern.getTrack(c).getEffect(r, k) == Effect.MOD_PATTERN_BREAK) { pattlen = r; break body; } } } } MidiPart[] parts = new MidiPart[trackcount]; for (int c = 0; c < lanes.length; c++) { parts[c] = new MidiPart(lanes[c]); parts[c].setStartTick(tickpos); parts[c].setEndTick(tickpos + res*pattlen); } for (int r = 0; r < pattlen; r++) { System.out.print(r + "# "); for (int c = 0; c < pattern.getTrackCount(); c++) { System.out.print(pattern.getTrack(c).getInfo(r)+ " "); int note = pattern.getTrack(c).getNote(r); if (note != Instrument.NO_NOTE) { // End previous note: if(active_noteevents[c] != null) { active_noteevents[c].setDuration(tickpos - active_noteevents[c].getStartTick()); active_noteevents[c].getPart().add(active_noteevents[c]); } if(note > 0) { // Add new note active_noteevents[c] = new NoteEvent(parts[c], tickpos, note, 100, 0, 1*res); } } /* int newNote = getNote(pattern, division, trackNumber); int newInstrument = getInstrument(pattern, division, trackNumber); if (newNote != Instrument.NO_NOTE && !noteIsArgument(pattern, division, trackNumber) && newInstrument != Track.NO_INSTRUMENT) newNoteAndInstrument(newNote, newInstrument); else if ( newNote != Instrument.NO_NOTE && !noteIsArgument(pattern, division, trackNumber) && newInstrument == Track.NO_INSTRUMENT) newNote(newNote); else if ( (newNote == Instrument.NO_NOTE || noteIsArgument(pattern, division, trackNumber)) && newInstrument != Track.NO_INSTRUMENT) newInstrument(newInstrument); / // System.out.print(pattern.getTrack(j).getInstrumentNumber(i)+ " "); } System.out.println(); tickpos += (long)res; } } for (int c = 0; c < active_noteevents.length; c++) { if(active_noteevents[c] != null) { active_noteevents[c].setDuration(tickpos - active_noteevents[c].getStartTick()); active_noteevents[c].getPart().add(active_noteevents[c]); } } // Create a lane /* MidiLane lane = project.createMidiLane(); // Create a MidiPart MidiPart part = new MidiPart(lane); // Add some notes part.add(new NoteEvent(part, 0, 60, 100, 0, 70*127)); part.add(new NoteEvent(part, 128, 61, 100, 0, 2*127)); part.add(new NoteEvent(part, 256, 62, 100, 0, 127)); part.add(new NoteEvent(part, 512, 63, 100, 0, 3*127)); part.add(new NoteEvent(part, 768, 64, 100, 0, 30*127)); part.setBoundsFromEvents(); lane=project.createMidiLane(); // Create a second part on the same lane part = new MidiPart(lane); // Add some notes part.add(new NoteEvent(part, 1024 + 0, 60, 100, 0, 127)); part.add(new NoteEvent(part, 1024 + 128, 59, 100, 0, 127)); part.add(new NoteEvent(part, 1024 + 256, 58, 100, 0, 127)); part.add(new NoteEvent(part, 1024 + 512, 57, 100, 0, 127)); part.add(new NoteEvent(part, 1024 + 768, 56, 100, 0, 127)); part.setBoundsFromEvents(); * project.validate(); } */ static class FRState { boolean active = false; boolean newNoteEvent = false; short[] sampleData = null; double offset = 0; double rate = 0; double volume = 0; double panning = 0; int loopType = 0; int loopStart = 0; int loopLength = 0; } static class FRMixer implements Mixer { FRState[] tracks; public FRMixer(int trackcount) { tracks = new FRState[trackcount]; for (int i = 0; i < tracks.length; i++) { tracks[i] = new FRState(); } } public void setTrack( short[] sampleData, double offset, double rate, double volume, double panning, int loopType, int loopStart, int loopLength, int track) throws PlayerException { if(track >= tracks.length) return; tracks[track].newNoteEvent = false; if(sampleData != null) if(tracks[track].sampleData == null) { tracks[track].newNoteEvent = true; tracks[track].active = true; } if(tracks[track].offset > offset) { tracks[track].newNoteEvent = true; tracks[track].active = true; } tracks[track].sampleData = sampleData; tracks[track].offset = offset; tracks[track].rate = rate; tracks[track].volume = volume; tracks[track].panning = panning; tracks[track].loopType = loopType; tracks[track].loopStart = loopStart; tracks[track].loopLength = loopLength; if(sampleData == null) { tracks[track].active = false; return; } if(tracks[track].active) if(tracks[track].loopType == 0) { if(tracks[track].offset == sampleData.length) { tracks[track].active = false; } } } double lastplaytime = 0; public void play(double arg0) throws PlayerException { lastplaytime = arg0; } public int getNumberOfTracks() { return 0; } public void setAmplification(double arg0) { } public double getAmplification() { return 0; } public void setVolume(double arg0) { } public double getVolume() { return 0; } public void setBalance(double arg0) { } public double getBalance() { return 0; } public void setSeparation(double arg0) { } public double getSeparation() { return 0; } public void setMute(int arg0, boolean arg1) { } public boolean isMute(int arg0) { return false; } } public static void main(String[] args) throws Exception { //File file = new File("S:\\Java\\eclipse\\workspace\\jmod\\www\\testdata\\moscow.mod"); //File file = new File("C:\\Documents and Settings\\kalli\\My Documents\\spacedeb.mod"); File file = new File("S:\\Java\\eclipse\\workspace\\jmod\\www\\testdata\\gbcoll.mod"); //hardwired.xm,moscow.mod,odyssey1.mod ModuleLoader modloader = ModuleLoader.getModuleLoader(file); Module module = modloader.getModule(); System.out.println("Init BPM: " + module.getInitialBpm()); /* System.out.println(); System.out.println("Instruments"); System.out.println("==========="); Instrument[] instruments = module.getInstruments(); for (int i = 0; i < instruments.length; i++) { Instrument ins = instruments[i]; System.out.println("name: " + ins.getName()); System.out.println("samples: " + ins.getNumberOfSamples()); System.out.println(); System.out.println("sample.length: " + ins.getSampleByNum(0).getLength()); System.out.println("sample.note: " + ins.getSampleByNum(0).getRelativeNote()); System.out.println("sample.tune: " + ins.getSampleByNum(0).getFineTune()); } System.out.println(); System.out.println("Orderlist"); System.out.println("========="); for (int i = 0; i < module.getNumberOfPositions(); i++) { System.out.println("pattern" + module.getPatternIndexAtPos(i)); } System.out.println(); System.out.println("Pattern0"); System.out.println("========"); Pattern pattern = module.getPattern(0); System.out.println("tracks: " + pattern.getTrackCount()); for (int i = 0; i < pattern.getDivisions(); i++) { System.out.print(i + "# "); for (int j = 0; j < pattern.getTrackCount(); j++) { System.out.print(pattern.getTrack(j).getInfo(i)+ " "); //System.out.print(pattern.getTrack(j).getInstrumentNumber(i)+ " "); } System.out.println(); } */ FRMixer mixer = new FRMixer(module.getTrackCount()); ModuleState ms = new ModuleState(module, mixer); TrackState[] ts = null; // trackStates is private variable, so we just cheat a little // to access it Field field; try { field = ms.getClass().getDeclaredField("trackStates"); field.setAccessible(true); ts = (TrackState[])field.get(ms); } catch (Exception e1) { e1.printStackTrace(); } try { System.out.println(ms.getPosition() + ":" + ms.getDivision() + ":" + ms.getTick() + " first"); boolean engage = false; boolean ok = true; int last_position = ms.getPosition(); TreeSet<String> loop_detection = new TreeSet<String>(); String pos_id = ms.getPosition() + ":" + ms.getDivision(); loop_detection.add(pos_id); int beat = 0; while(ok) { int tick = ms.getTick(); if(last_position != ms.getPosition()) { System.out.println(); System.out.println(); } last_position = ms.getPosition(); ok = ms.play(); if(tick == 0) { // In midi , let 4 divison be a beat, where one divison is a quarter note // Let's calculate BPM from that, lastplaytime is in msec // beat/ms ms * 1000 = sec // double bpm = 60000.0 / (mixer.lastplaytime*ms.getTicksInDivision()*4.0); //System.out.print((mixer.lastplaytime*ms.getTicksInDivision()) + " "); System.out.print((((int)bpm)+ "bpm " + (beat/4.0) + " ").substring(0, 16)); System.out.print((ms.getPosition() + ":" + ms.getDivision() + ":" + tick+ " ").substring(0, 10)); System.out.print(" "); for (int i = 0; i < mixer.tracks.length; i++) { double baserate = 0; Sample sample = ts[i].getSample(); if(sample != null) if(ts[i].getNote() != Instrument.NO_NOTE) { baserate = sample.getUnits().note2rate(ts[i].getNote() + sample.getFineTune() + sample.getRelativeNote()); } if(baserate == 0 || !mixer.tracks[i].active) { System.out.print((" ").substring(0, 10)); } else { if(!mixer.tracks[i].newNoteEvent) System.out.print(" -- -" + ("- ").substring(0, 2)); else System.out.print(" " + ts[i].getNote() + ":i" + (ts[i].getInstrument()+" ").substring(0, 2)); System.out.print( ( (" p"+mixer.tracks[i].rate/baserate).substring(0,5) + (" v"+mixer.tracks[i].volume).substring(0,5) + (" pn"+mixer.tracks[i].panning).substring(0,6) + " ").substring(0, 18)); } } System.out.println(); beat += 1; } if(ms.getTick() == 0) { pos_id = ms.getPosition() + ":" + ms.getDivision(); if(loop_detection.contains(pos_id)) { System.out.println("LOOP detected, break"); break; } loop_detection.add(pos_id); } /* if(!engage) { if(first_position != ms.getPosition()) { engage = true; } } else { if(first_position == ms.getPosition()) { System.out.println("We are looping"); break; } } */ } } catch (PlayerException e) { System.out.println("error in player"); e.printStackTrace(); } } }