├── .gitignore ├── res ├── cd-icon.png ├── song-icon.png ├── 3DPrinter128.png ├── PrinterArea100.png └── PrinterMargins100.png ├── test time.mid ├── bin └── song-icon.png ├── pokemon_center.mid ├── .settings ├── org.eclipse.m2e.core.prefs ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── src └── org │ ├── warp │ └── midito3d │ │ ├── music │ │ ├── midi │ │ │ ├── MidiMusicEvent.java │ │ │ ├── PitchEvent.java │ │ │ ├── TempoEvent.java │ │ │ ├── ProgramEvent.java │ │ │ ├── MainVolumeEvent.java │ │ │ ├── MidiProgram.java │ │ │ ├── MidiNote.java │ │ │ ├── NoteEvent.java │ │ │ ├── MidiParser.java │ │ │ ├── Channel.java │ │ │ └── DoubleSequence.java │ │ ├── mp3 │ │ │ ├── WindowFunction.java │ │ │ ├── NullWindowFunction.java │ │ │ ├── Mp3Note.java │ │ │ ├── OverlapBuffer.java │ │ │ ├── VorbisWindowFunction.java │ │ │ ├── Frame.java │ │ │ ├── FFT.java │ │ │ ├── Clip.java │ │ │ ├── Mp3Music.java │ │ │ ├── Complex.java │ │ │ └── Mp3Parser.java │ │ ├── DoneListener.java │ │ ├── Note.java │ │ └── Music.java │ │ ├── gui │ │ ├── SongPanel.java │ │ ├── JButton.java │ │ ├── printers │ │ │ ├── PrinterModel.java │ │ │ ├── StandardPrinters.java │ │ │ ├── MotorSetting.java │ │ │ ├── PrinterModelArea.java │ │ │ ├── Model2Axes.java │ │ │ ├── ModelZAxis.java │ │ │ ├── Model3Axes.java │ │ │ └── Model4Axes.java │ │ ├── OpenSongPanel.java │ │ ├── JImage.java │ │ ├── ModernDialog.java │ │ ├── InputSongPanel.java │ │ ├── MainWindow.java │ │ └── PrinterPanel.java │ │ ├── printers │ │ ├── Motor.java │ │ ├── PrinterZAxis.java │ │ ├── Printer2Axes.java │ │ ├── Printer3Axes.java │ │ ├── Printer4Axes.java │ │ ├── GCodeOutput.java │ │ ├── Printer.java │ │ └── PrinterNAxes.java │ │ ├── Main.java │ │ ├── PrinterArea.java │ │ ├── CommandLineManager.java │ │ ├── FilenameUtils.java │ │ └── Midi23D.java │ └── jfugue │ ├── ParserProgressListener.java │ ├── MidiMessageRecipient.java │ ├── Measure.java │ ├── JFugueElement.java │ ├── Voice.java │ ├── Time.java │ ├── ChannelPressure.java │ ├── Layer.java │ ├── PitchBend.java │ ├── PolyphonicPressure.java │ ├── Tempo.java │ ├── TimeEventManager.java │ ├── KeySignature.java │ ├── ParserListener.java │ ├── TimeFactor.java │ ├── Controller.java │ ├── MidiParser.java │ └── Parser.java ├── .gitattributes ├── .project ├── .classpath ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | -------------------------------------------------------------------------------- /res/cd-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavallium/Midi23D/HEAD/res/cd-icon.png -------------------------------------------------------------------------------- /test time.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavallium/Midi23D/HEAD/test time.mid -------------------------------------------------------------------------------- /bin/song-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavallium/Midi23D/HEAD/bin/song-icon.png -------------------------------------------------------------------------------- /res/song-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavallium/Midi23D/HEAD/res/song-icon.png -------------------------------------------------------------------------------- /pokemon_center.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavallium/Midi23D/HEAD/pokemon_center.mid -------------------------------------------------------------------------------- /res/3DPrinter128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavallium/Midi23D/HEAD/res/3DPrinter128.png -------------------------------------------------------------------------------- /res/PrinterArea100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavallium/Midi23D/HEAD/res/PrinterArea100.png -------------------------------------------------------------------------------- /res/PrinterMargins100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cavallium/Midi23D/HEAD/res/PrinterMargins100.png -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/MidiMusicEvent.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | interface MidiMusicEvent { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | encoding/res=UTF-8 4 | encoding/src=UTF-8 5 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/WindowFunction.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | public interface WindowFunction { 4 | public void applyWindow(double[] var1); 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/NullWindowFunction.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | public class NullWindowFunction 4 | implements WindowFunction { 5 | public void applyWindow(double[] data) { 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/DoneListener.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music; 2 | 3 | import java.io.IOException; 4 | import java.util.EventListener; 5 | 6 | public interface DoneListener extends EventListener { 7 | public void done(); 8 | } 9 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/PitchEvent.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | class PitchEvent implements MidiMusicEvent { 4 | public final double pitch; 5 | 6 | public PitchEvent(double pitch) { 7 | this.pitch = pitch; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/TempoEvent.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | class TempoEvent implements MidiMusicEvent { 4 | public final double tempo; 5 | 6 | public TempoEvent(double tempo) { 7 | this.tempo = tempo; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/ProgramEvent.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | class ProgramEvent implements MidiMusicEvent { 4 | public final int program; 5 | 6 | public ProgramEvent(int program) { 7 | this.program = program; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/SongPanel.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui; 2 | 3 | import javax.swing.JPanel; 4 | 5 | public abstract class SongPanel extends JPanel { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = -4699621446486185519L; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/MainVolumeEvent.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | class MainVolumeEvent implements MidiMusicEvent { 4 | 5 | public final double volume; 6 | 7 | public MainVolumeEvent(double volume) { 8 | this.volume = volume; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/MidiProgram.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | class MidiProgram { 4 | 5 | public final int id; 6 | public final int duration; 7 | 8 | public MidiProgram(int id, int duration) { 9 | this.id = id; 10 | this.duration = duration; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/JButton.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui; 2 | 3 | public class JButton extends javax.swing.JButton { 4 | 5 | private static final long serialVersionUID = 2921854487203807147L; 6 | 7 | 8 | public JButton(String text) { 9 | super(text); 10 | this.setOpaque(false); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/Mp3Note.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | import org.warp.midito3d.music.Note; 4 | 5 | public class Mp3Note extends Note { 6 | 7 | public Mp3Note(double note, double velocity, long startTick) { 8 | super(note, velocity, startTick); 9 | } 10 | 11 | @Override 12 | public double getFrequency() { 13 | return note; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/MidiNote.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | import org.warp.midito3d.music.Note; 4 | 5 | class MidiNote extends Note { 6 | 7 | public MidiNote(double frequency, double velocity, long startTick) { 8 | super(frequency, velocity, startTick); 9 | } 10 | 11 | @Override 12 | public double getFrequency() { 13 | return note; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/Note.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music; 2 | 3 | public abstract class Note { 4 | protected final double note; 5 | public final double velocity; 6 | public final long startTick; 7 | 8 | public Note(double note, double velocity, long startTick) { 9 | this.note = note; 10 | this.velocity = velocity; 11 | this.startTick = startTick; 12 | } 13 | 14 | public abstract double getFrequency(); 15 | } 16 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/NoteEvent.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | class NoteEvent implements MidiMusicEvent { 4 | public final boolean state; 5 | public final int note; 6 | public final int velocity; 7 | 8 | public NoteEvent(boolean state, int note, int velocity) { 9 | if (velocity == 0) { 10 | state = false; 11 | } 12 | this.state = state; 13 | this.note = note; 14 | this.velocity = velocity; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization. 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/printers/PrinterModel.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui.printers; 2 | 3 | import org.warp.midito3d.printers.Printer; 4 | 5 | public interface PrinterModel { 6 | public int getMotorsCount(); 7 | // take the motor configuration (steps/mm) from DEFAULT_AXIS_STEPS_PER_UNIT 8 | public MotorSetting getMotor(int number); 9 | public String getName(); 10 | public String getMotorName(int i); 11 | public Printer createPrinterObject(PrinterModelArea printerModelArea); 12 | } 13 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/printers/Motor.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.printers; 2 | 3 | public class Motor { 4 | private final int stepsPerMillimeter; 5 | 6 | /** 7 | * @param stepsPerMillimeter steps/mm, usually taken from DEFAULT_AXIS_STEPS_PER_UNIT 8 | */ 9 | public Motor(int stepsPerMillimeter) { 10 | this.stepsPerMillimeter = stepsPerMillimeter; 11 | } 12 | 13 | /** 14 | * steps/mm 15 | */ 16 | public int getStepsPerMillimeter() { 17 | return stepsPerMillimeter; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/printers/StandardPrinters.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui.printers; 2 | 3 | public class StandardPrinters { 4 | 5 | public static final MotorSetting[] ANET_A8_DEFAULT_AXIS_STEPS_PER_MM = new MotorSetting[]{new MotorSetting(100), new MotorSetting(100), new MotorSetting(400), new MotorSetting(100)}; 6 | public static final MotorSetting[] LABISTS_ET4_DEFAULT_AXIS_STEPS_PER_MM = new MotorSetting[]{new MotorSetting(80), new MotorSetting(80), new MotorSetting(400), new MotorSetting(500)}; 7 | } 8 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/Main.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d; 2 | 3 | import com.formdev.flatlaf.FlatLightLaf; 4 | import com.formdev.flatlaf.util.UIScale; 5 | import javax.swing.UIManager; 6 | import org.warp.midito3d.gui.MainWindow; 7 | 8 | public class Main { 9 | 10 | public static void main(String[] args) { 11 | if (args.length <= 2) { 12 | try { 13 | FlatLightLaf.setup(); 14 | } catch (Exception e) { 15 | e.printStackTrace(); 16 | // handle exception 17 | } 18 | new MainWindow().execute(); 19 | } else { 20 | CommandLineManager.execute(args); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/printers/MotorSetting.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui.printers; 2 | 3 | import org.warp.midito3d.printers.Motor; 4 | 5 | public class MotorSetting { 6 | 7 | /** 8 | * steps/mm, usually taken from DEFAULT_AXIS_STEPS_PER_UNIT 9 | */ 10 | public int ppi; 11 | 12 | /** 13 | * @param ppi steps/mm, usually taken from DEFAULT_AXIS_STEPS_PER_UNIT 14 | */ 15 | public MotorSetting(int ppi) { 16 | this.ppi = ppi; 17 | } 18 | 19 | public MotorSetting() { 20 | this.ppi = 100; 21 | } 22 | 23 | public Motor createMotorObject() { 24 | return new Motor(ppi); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Midi23D 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.m2e.core.maven2Nature 21 | org.eclipse.jdt.core.javanature 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/printers/PrinterModelArea.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui.printers; 2 | 3 | import org.warp.midito3d.PrinterArea; 4 | 5 | public class PrinterModelArea { 6 | public int[] size; 7 | public int[] margins; 8 | 9 | /** 10 | * @param size 11 | * @param margins 12 | */ 13 | public PrinterModelArea(int[] size, int[] margins) { 14 | this.size = size; 15 | this.margins = margins; 16 | } 17 | 18 | public PrinterArea createAreaObject() { 19 | return new PrinterArea(new int[] {margins[0], margins[1], margins[2], margins[3]}, new int[] {size[0]-margins[0], size[1]-margins[1], size[2]-margins[2], size[3]-margins[3]}); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/printers/PrinterZAxis.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.printers; 2 | 3 | import java.io.IOException; 4 | import java.util.Locale; 5 | import org.warp.midito3d.PrinterArea; 6 | 7 | public class PrinterZAxis extends PrinterNAxes { 8 | 9 | public PrinterZAxis(Motor z, PrinterArea printerArea) { 10 | super(new Motor[]{z}, printerArea); 11 | } 12 | 13 | @Override 14 | protected void writeGMove(GCodeOutput po, boolean fastMove, double feed, double[] motorsPosition) throws IOException { 15 | if (!fastMove) { 16 | po.writeLine(String.format(Locale.US, "G01 F%.10f", feed)); 17 | } 18 | po.writeLine(String.format(Locale.US, "G0%s Z%.10f", fastMove ? "0" : "1", feed, motorsPosition[0])); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/MidiParser.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import javax.sound.midi.InvalidMidiDataException; 6 | import javax.sound.midi.MidiSystem; 7 | import javax.sound.midi.Sequence; 8 | 9 | public class MidiParser { 10 | 11 | public static MidiMusic loadFrom(String string) throws InvalidMidiDataException, IOException { 12 | return loadFrom(string, false); 13 | } 14 | 15 | public static MidiMusic loadFrom(String string, boolean debug) throws InvalidMidiDataException, IOException { 16 | Sequence sequence = MidiSystem.getSequence(new File(string)); 17 | MidiMusic m = new MidiMusic(sequence, debug); 18 | return m; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/PrinterArea.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d; 2 | 3 | public class PrinterArea { 4 | /** 5 | * x, y, z, ... 6 | */ 7 | public final int[] min; 8 | /** 9 | * x, y, z, ... 10 | */ 11 | public final int[] max; 12 | 13 | public PrinterArea(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { 14 | this(3); 15 | this.min[0] = minX; 16 | this.min[1] = minY; 17 | this.min[2] = minZ; 18 | this.max[0] = maxX; 19 | this.max[1] = maxY; 20 | this.max[2] = maxZ; 21 | } 22 | 23 | public PrinterArea(int nd) { 24 | this.min = new int[nd]; 25 | this.max = new int[nd]; 26 | } 27 | 28 | public PrinterArea(int[] min, int[] max) { 29 | assert min.length == max.length; 30 | this.min = min; 31 | this.max = max; 32 | } 33 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/Music.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music; 2 | 3 | import java.util.List; 4 | 5 | public interface Music { 6 | 7 | void setOutputChannelsCount(int i); 8 | 9 | void reanalyze(DoneListener l); 10 | 11 | boolean hasNext(); 12 | 13 | void findNext(); 14 | 15 | long getCurrentTick(); 16 | 17 | long getLength(); 18 | 19 | double getDivision(); 20 | 21 | double getCurrentTempo(); 22 | 23 | Note getCurrentNote(int channel); 24 | 25 | double getChannelPitch(int channel); 26 | 27 | double getSpeedMultiplier(); 28 | 29 | void setSpeedMultiplier(double f); 30 | 31 | float getToneMultiplier(); 32 | 33 | void setToneMultiplier(float f); 34 | 35 | void setBlacklistedChannels(List blacklistedChannels); 36 | 37 | void setDebugOutput(boolean b); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/printers/Printer2Axes.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.printers; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.Locale; 6 | import org.warp.midito3d.PrinterArea; 7 | 8 | public class Printer2Axes extends PrinterNAxes { 9 | 10 | public Printer2Axes(Motor x, Motor y, PrinterArea printerArea) { 11 | super(new Motor[]{x, y}, printerArea); 12 | } 13 | 14 | @Override 15 | protected void writeGMove(GCodeOutput po, boolean fastMove, double feed, double[] motorsPosition) throws IOException { 16 | if (!fastMove) { 17 | po.writeLine(String.format(Locale.US, "G01 F%.10f", feed)); 18 | } 19 | po.writeLine(String.format(Locale.US, "G0%s F%.10f X%.10f Y%.10f Z2.00", fastMove ? "0" : "1", feed, motorsPosition[0], motorsPosition[1])); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/printers/Printer3Axes.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.printers; 2 | 3 | import java.io.IOException; 4 | import java.util.Locale; 5 | import org.warp.midito3d.PrinterArea; 6 | 7 | public class Printer3Axes extends PrinterNAxes { 8 | 9 | public Printer3Axes(Motor x, Motor y, Motor z, PrinterArea printerArea) { 10 | super(new Motor[]{x, y, z}, printerArea); 11 | } 12 | 13 | @Override 14 | protected void writeGMove(GCodeOutput po, boolean fastMove, double feed, double[] motorsPosition) throws IOException { 15 | if (!fastMove) { 16 | po.writeLine(String.format(Locale.US, "G01 F%.10f", feed)); 17 | } 18 | po.writeLine(String.format(Locale.US, "G0%s F%.10f X%.10f Y%.10f Z%.10f", fastMove ? "0" : "1", feed, motorsPosition[0], motorsPosition[1], motorsPosition[2])); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/printers/Printer4Axes.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.printers; 2 | 3 | import java.io.IOException; 4 | import java.util.Locale; 5 | import org.warp.midito3d.PrinterArea; 6 | 7 | public class Printer4Axes extends PrinterNAxes { 8 | 9 | public Printer4Axes(Motor x, Motor y, Motor z, Motor e, PrinterArea printerArea) { 10 | super(new Motor[]{x, y, z, e}, printerArea); 11 | } 12 | 13 | @Override 14 | protected void writeGMove(GCodeOutput po, boolean fastMove, double feed, double[] motorsPosition) throws IOException { 15 | if (!fastMove) { 16 | po.writeLine(String.format(Locale.US, "G01 F%.10f", feed)); 17 | } 18 | po.writeLine(String.format(Locale.US, "G0%s F%.10f X%.10f Y%.10f Z%.10f E%.10f", fastMove ? "0" : "1", feed, motorsPosition[0], motorsPosition[1], motorsPosition[2], motorsPosition[3])); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/printers/GCodeOutput.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.printers; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.net.URISyntaxException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | 10 | public class GCodeOutput { 11 | 12 | Path path; 13 | BufferedWriter bw; 14 | 15 | public GCodeOutput(String path) throws URISyntaxException { 16 | this.path = new File(path).toPath(); 17 | } 18 | 19 | public GCodeOutput(File path) { 20 | this.path = path.toPath(); 21 | } 22 | 23 | public void openAndLock() throws IOException { 24 | Files.deleteIfExists(path); 25 | Files.createFile(path); 26 | bw = Files.newBufferedWriter(path); 27 | bw.flush(); 28 | } 29 | 30 | public void write(String string) throws IOException { 31 | bw.write(string); 32 | } 33 | 34 | public void writeLine(String string) throws IOException { 35 | write(string+"\n"); 36 | } 37 | 38 | public void close() throws IOException { 39 | bw.flush(); 40 | bw.close(); 41 | bw = null; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/Channel.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.Map.Entry; 6 | 7 | class Channel { 8 | public final HashMap> events; //Tick, NoteEvent 9 | public HashMap activeNotes; //Note, Note 10 | public double currentVolume; 11 | public double currentPitch; 12 | public int currentProgram; 13 | 14 | public Channel() { 15 | events = new HashMap<>(); 16 | activeNotes = new HashMap<>(); 17 | currentVolume = 1d; 18 | currentProgram = 0; 19 | currentPitch = 1; 20 | } 21 | 22 | public boolean isSilent() { 23 | boolean silent = true; 24 | search : for (Entry> eventEntry : events.entrySet()) { 25 | ArrayList events = eventEntry.getValue(); 26 | for (MidiMusicEvent event : events) { 27 | if (event instanceof NoteEvent) { 28 | if (((NoteEvent)event).state == true) { 29 | silent = false; 30 | break search; 31 | } 32 | } 33 | } 34 | } 35 | return silent; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/printers/Printer.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.printers; 2 | 3 | import java.io.IOException; 4 | 5 | public interface Printer { 6 | public int getMotorsCount(); 7 | public void initialize(GCodeOutput po) throws IOException; 8 | 9 | /** 10 | * @param time seconds 11 | */ 12 | public void wait(GCodeOutput po, double time) throws IOException; 13 | 14 | /** 15 | * 16 | * @param time seconds 17 | * @param motorSpeed mm/min 18 | */ 19 | public void move(GCodeOutput po, double time, double... motorSpeed) throws IOException; 20 | 21 | /** 22 | * @param time seconds 23 | */ 24 | public void goTo(GCodeOutput po, double time, double... position) throws IOException; 25 | public void stop(GCodeOutput po) throws IOException; 26 | public Motor getMotor(int number); 27 | public boolean isBiggerThanMax(int motor, double val); 28 | public boolean isSmallerThanMin(int motor, double val); 29 | 30 | default double euclideanDistance(double[] a, double[] b) { 31 | double x = 0, s; 32 | for (int i = 0; i < a.length; x += s * s) { 33 | s = a[i] - b[i++]; 34 | } 35 | return Math.sqrt(x); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/org/jfugue/ParserProgressListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | import java.util.EventListener; 26 | 27 | public interface ParserProgressListener extends EventListener 28 | { 29 | public void progressReported(String description, long partCompleted, long whole); 30 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/printers/Model2Axes.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui.printers; 2 | 3 | import java.util.Arrays; 4 | import org.warp.midito3d.printers.Printer2Axes; 5 | 6 | public class Model2Axes implements PrinterModel { 7 | 8 | private final String modelName; 9 | private final MotorSetting[] motors; 10 | 11 | public Model2Axes(String modelName, MotorSetting[] defaultMotorSetting) { 12 | this.modelName = modelName; 13 | motors = Arrays.copyOf(defaultMotorSetting, 2); 14 | } 15 | 16 | @Override 17 | public int getMotorsCount() { 18 | return 2; 19 | } 20 | 21 | @Override 22 | public MotorSetting getMotor(int number) { 23 | return motors[number]; 24 | } 25 | 26 | @Override 27 | public String getName() { 28 | return "XY Axes (" + modelName + ")"; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return getName(); 34 | } 35 | 36 | @Override 37 | public String getMotorName(int i) { 38 | switch (i) { 39 | case 0: 40 | return "Motor X"; 41 | case 1: 42 | return "Motor Y"; 43 | default: 44 | return "err"; 45 | } 46 | } 47 | 48 | @Override 49 | public Printer2Axes createPrinterObject(PrinterModelArea printerModelArea) { 50 | return new Printer2Axes(motors[0].createMotorObject(), motors[1].createMotorObject(), printerModelArea.createAreaObject()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/org/jfugue/MidiMessageRecipient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | import javax.sound.midi.MidiMessage; 26 | 27 | /** 28 | * Used in conjunction with TimeFactor.sortAndDeliverMidiMessages; 29 | * handles MIDI messages when they are delivered. 30 | * 31 | * @author David Koelle 32 | * 33 | */ 34 | public interface MidiMessageRecipient 35 | { 36 | public void messageReady(MidiMessage message, long timestamp); 37 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/OverlapBuffer.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | import java.util.LinkedList; 4 | 5 | public class OverlapBuffer { 6 | private final LinkedList buffers; 7 | private final double[] emptyFrame; 8 | private final int offset; 9 | private int current; 10 | 11 | public OverlapBuffer(int frameSize, int overlap) { 12 | this.offset = frameSize / overlap; 13 | this.emptyFrame = new double[frameSize]; 14 | this.buffers = new LinkedList(); 15 | for (int i = 0; i < overlap; ++i) { 16 | this.buffers.add(this.emptyFrame); 17 | } 18 | } 19 | 20 | public double next() { 21 | int myOffset = this.current; 22 | double val = 0.0; 23 | for (double[] buf : this.buffers) { 24 | val += buf[myOffset]; 25 | myOffset += this.offset; 26 | } 27 | ++this.current; 28 | return val; 29 | } 30 | 31 | public void addFrame(double[] frame) { 32 | this.buffers.addFirst(frame); 33 | this.buffers.removeLast(); 34 | this.current = 0; 35 | } 36 | 37 | public void addEmptyFrame() { 38 | this.addFrame(this.emptyFrame); 39 | } 40 | 41 | public boolean needsNewFrame() { 42 | return this.current == this.offset; 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/printers/ModelZAxis.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui.printers; 2 | 3 | import java.util.Arrays; 4 | import org.warp.midito3d.printers.PrinterZAxis; 5 | 6 | public class ModelZAxis implements PrinterModel { 7 | 8 | private final String modelName; 9 | private final MotorSetting[] motors; 10 | 11 | public ModelZAxis(String modelName, MotorSetting[] defaultMotorSetting) { 12 | this.modelName = modelName; 13 | motors = Arrays.copyOfRange(defaultMotorSetting, 2, 3); 14 | } 15 | 16 | @Override 17 | public int getMotorsCount() { 18 | return 1; 19 | } 20 | 21 | @Override 22 | public MotorSetting getMotor(int number) { 23 | if (number == 0) { 24 | return motors[0]; 25 | } else { 26 | throw new java.lang.IndexOutOfBoundsException(); 27 | } 28 | } 29 | 30 | @Override 31 | public String getName() { 32 | return "Z Axis (" + modelName + ")"; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return getName(); 38 | } 39 | 40 | @Override 41 | public String getMotorName(int i) { 42 | switch (i) { 43 | case 0: 44 | return "Motor Z"; 45 | default: 46 | return "err"; 47 | } 48 | } 49 | 50 | @Override 51 | public PrinterZAxis createPrinterObject(PrinterModelArea printerModelArea) { 52 | return new PrinterZAxis(motors[0].createMotorObject(), printerModelArea.createAreaObject()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/VorbisWindowFunction.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | import java.util.Arrays; 4 | import java.util.logging.Logger; 5 | 6 | public class VorbisWindowFunction 7 | implements WindowFunction { 8 | private static final Logger logger = Logger.getLogger(VorbisWindowFunction.class.getName()); 9 | private final double[] scalars; 10 | private static final double PI = 3.141592653589793; 11 | 12 | public VorbisWindowFunction(int size) { 13 | this.scalars = new double[size]; 14 | for (int i = 0; i < size; ++i) { 15 | double xx = Math.sin(3.141592653589793 / (2.0 * (double)size) * (2.0 * (double)i)); 16 | this.scalars[i] = Math.sin(1.5707963267948966 * (xx * xx)); 17 | } 18 | logger.finest(String.format("VorbisWindowFunction scalars (size=%d): %s\n", this.scalars.length, Arrays.toString(this.scalars))); 19 | } 20 | 21 | public void applyWindow(double[] data) { 22 | if (data.length != this.scalars.length) { 23 | throw new IllegalArgumentException("Invalid array size (required: " + this.scalars.length + "; given: " + data.length + ")"); 24 | } 25 | for (int i = 0; i < data.length; ++i) { 26 | double[] arrd = data; 27 | int n = i; 28 | arrd[n] = arrd[n] * this.scalars[i]; 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/printers/Model3Axes.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui.printers; 2 | 3 | import java.util.Arrays; 4 | import org.warp.midito3d.printers.Printer3Axes; 5 | 6 | public class Model3Axes implements PrinterModel { 7 | 8 | private final String modelName; 9 | private final MotorSetting[] motors; 10 | 11 | public Model3Axes(String modelName, MotorSetting[] defaultMotorSetting) { 12 | this.modelName = modelName; 13 | motors = Arrays.copyOf(defaultMotorSetting, 3); 14 | } 15 | 16 | @Override 17 | public int getMotorsCount() { 18 | return 3; 19 | } 20 | 21 | @Override 22 | public MotorSetting getMotor(int number) { 23 | return motors[number]; 24 | } 25 | 26 | @Override 27 | public String getName() { 28 | return "XYZ Axes (" + modelName+ ")"; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return getName(); 34 | } 35 | 36 | @Override 37 | public String getMotorName(int i) { 38 | switch (i) { 39 | case 0: 40 | return "Motor X"; 41 | case 1: 42 | return "Motor Y"; 43 | case 2: 44 | return "Motor Z"; 45 | default: 46 | return "err"; 47 | } 48 | } 49 | 50 | @Override 51 | public Printer3Axes createPrinterObject(PrinterModelArea printerModelArea) { 52 | return new Printer3Axes(motors[0].createMotorObject(), motors[1].createMotorObject(), motors[2].createMotorObject(), printerModelArea.createAreaObject()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/printers/Model4Axes.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui.printers; 2 | 3 | import java.util.Arrays; 4 | import org.warp.midito3d.printers.Printer4Axes; 5 | 6 | public class Model4Axes implements PrinterModel { 7 | 8 | private final String modelName; 9 | private final MotorSetting[] motors; 10 | 11 | public Model4Axes(String modelName, MotorSetting[] defaultMotorSetting) { 12 | this.modelName = modelName; 13 | motors = Arrays.copyOf(defaultMotorSetting, 4); 14 | } 15 | 16 | @Override 17 | public int getMotorsCount() { 18 | return 4; 19 | } 20 | 21 | @Override 22 | public MotorSetting getMotor(int number) { 23 | return motors[number]; 24 | } 25 | 26 | @Override 27 | public String getName() { 28 | return "XYZ Axes + Extruder (" + modelName + ")"; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return getName(); 34 | } 35 | 36 | @Override 37 | public String getMotorName(int i) { 38 | switch (i) { 39 | case 0: 40 | return "Motor X"; 41 | case 1: 42 | return "Motor Y"; 43 | case 2: 44 | return "Motor Z"; 45 | case 3: 46 | return "Extruder"; 47 | default: 48 | return "err"; 49 | } 50 | } 51 | 52 | @Override 53 | public Printer4Axes createPrinterObject(PrinterModelArea printerModelArea) { 54 | return new Printer4Axes(motors[0].createMotorObject(), motors[1].createMotorObject(), motors[2].createMotorObject(), motors[3].createMotorObject(), printerModelArea.createAreaObject()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/midi/DoubleSequence.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.midi; 2 | 3 | import java.util.HashMap; 4 | 5 | public class DoubleSequence { 6 | 7 | private HashMap frequencyChanges; 8 | private Long currentTick; 9 | private Double currentFrequency; 10 | 11 | public DoubleSequence() { 12 | frequencyChanges = new HashMap<>(); 13 | resetCounter(); 14 | } 15 | 16 | public void resetCounter() { 17 | currentTick = 0L; 18 | currentFrequency = 0d; 19 | } 20 | 21 | public void putEvent(long tick, double frequency) { 22 | frequencyChanges.put(tick, frequency); 23 | } 24 | 25 | public boolean hasEventAt(Long tick) { 26 | return frequencyChanges.containsKey(tick); 27 | } 28 | 29 | public double getEventAt(long tick) { 30 | return frequencyChanges.get(tick); 31 | } 32 | 33 | public double getComputedValueAt(long tick) { 34 | Double frequency; 35 | long currentTick = tick; 36 | do { 37 | frequency = frequencyChanges.getOrDefault(currentTick, null); 38 | currentTick--; 39 | } while (frequency == null && currentTick >= 0); 40 | if (frequency == null) { 41 | return 0; 42 | } else { 43 | return frequency; 44 | } 45 | } 46 | 47 | public double getNextTick() { 48 | if (frequencyChanges.containsKey(currentTick)) { 49 | currentFrequency = frequencyChanges.get(currentTick); 50 | } 51 | currentTick++; 52 | return currentFrequency==null?0:currentFrequency; 53 | } 54 | 55 | public boolean isEmpty() { 56 | return frequencyChanges.isEmpty(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/OpenSongPanel.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui; 2 | 3 | import java.awt.Color; 4 | import java.awt.Dimension; 5 | import java.awt.Font; 6 | import java.awt.GridBagConstraints; 7 | import java.awt.GridBagLayout; 8 | 9 | import javax.swing.BorderFactory; 10 | import javax.swing.JLabel; 11 | 12 | public class OpenSongPanel extends SongPanel { 13 | 14 | /** 15 | * 16 | */ 17 | private static final long serialVersionUID = 4440013224587289302L; 18 | 19 | public final JImage songIcon; 20 | public final JLabel description; 21 | 22 | public OpenSongPanel() { 23 | GridBagConstraints c = new GridBagConstraints(); 24 | 25 | super.setLayout(new GridBagLayout()); 26 | songIcon = JImage.loadFromResources("song-icon.png"); 27 | description = new JLabel("Please load a midi file"); 28 | 29 | songIcon.setMinimumSize(new Dimension(50, 50)); 30 | songIcon.setPreferredSize(new Dimension(100, 100)); 31 | songIcon.setMaximumSize(new Dimension(100, 100)); 32 | 33 | this.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 34 | this.setBackground(Color.white); 35 | 36 | c.gridx = 0; 37 | c.gridy = 0; 38 | add(songIcon, c); 39 | c.gridx = 0; 40 | c.gridy = 1; 41 | c.ipady = 10; 42 | c.fill = GridBagConstraints.HORIZONTAL; 43 | add(description, c); 44 | 45 | description.setForeground(Color.DARK_GRAY); 46 | } 47 | 48 | @Override 49 | public void setFont(Font f) { 50 | super.setFont(f); 51 | if (description != null) { 52 | description.setFont(f); 53 | } 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Midi23D 2 | **Midi23D** is a tool made in Java that converts every note of a .midi music into GCODE instructions to send directly to a 3D printer. 3 | 4 | ![GUI](https://cavallium.it/assets/midi23d/midi-gui.png) 5 | 6 | # How it works? 7 | Every 3D printer has 3 or more particular motors, called *stepper motors*. Despite of the regular DC motors their angular speed and rotation be controlled very precisely. 8 | 9 | Sending an impulse to that motors, in addition to result in a rotation, it will produce a sound, that it can be modulated by changing it. 10 | 11 | With the GCODE you can tell to the 3D printer extruder to go in a position with a determined speed. 12 | 13 | Setting the right speed for each motor by sending to the printer only the position and the total speed seems difficult, but it's quite easy. 14 | You must use this formula: https://cavallium.it/assets/midi23d/delta.svg 15 | 16 | In this way you can control simultaneously one note for each motor. 17 | 18 | # Usage 19 | First of all, download ***Midi23D*** ([***Cross-platform .jar file***](https://cavallium.it/assets/midi23d/Midi23D.jar)) 20 | 21 | Run the program by executing this code into your terminal: 22 | **java -jar Midi23D.jar ** 23 | (Motor-test is a boolean (true/false) parameter that if it's true it plays the same notes on all the motors. It helps when you try to accord each motor speed). 24 | 25 | Insert the parameters that asks to you. 26 | 27 | Drag and drop the generated file into Repetier Host or your program that controls your printer. 28 | 29 | Enjoy 30 | 31 | # [Download samples and MIDIs](https://cavallium.it/Midi23D) 32 | 33 | # Videos 34 | [![Video](http://img.youtube.com/vi/4rcnu8j1Xqk/0.jpg)](https://www.youtube.com/embed/4rcnu8j1Xqk) 35 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/Frame.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | import java.io.PrintStream; 4 | import java.util.Arrays; 5 | 6 | import org.jtransforms.dct.DoubleDCT_1D; 7 | 8 | public class Frame { 9 | private double[] data; 10 | private static DoubleDCT_1D dct; 11 | private final WindowFunction windowFunc; 12 | 13 | public Frame(double[] timeData, WindowFunction windowFunc) { 14 | this.windowFunc = windowFunc; 15 | if (dct == null) { 16 | dct = new DoubleDCT_1D(timeData.length); 17 | } 18 | windowFunc.applyWindow(timeData); 19 | dct.forward(timeData, true); 20 | this.data = new double[timeData.length]; 21 | for (int i = 0; i < this.data.length; ++i) { 22 | this.data[i] = timeData[i]; 23 | } 24 | } 25 | 26 | public int getLength() { 27 | return this.data.length; 28 | } 29 | 30 | public double getReal(int idx) { 31 | return this.data[idx]; 32 | } 33 | 34 | public double getImag(int idx) { 35 | return 0.0; 36 | } 37 | 38 | public void setReal(int idx, double d) { 39 | this.data[idx] = d; 40 | } 41 | 42 | public double[] asTimeData() { 43 | double[] timeData = new double[this.data.length]; 44 | System.arraycopy(this.data, 0, timeData, 0, this.data.length); 45 | dct.inverse(timeData, true); 46 | this.windowFunc.applyWindow(timeData); 47 | return timeData; 48 | } 49 | 50 | public static void main(String[] args) { 51 | double[] orig = new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 0.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 7.0}; 52 | System.out.println(Arrays.toString(orig)); 53 | Frame f = new Frame(orig, new NullWindowFunction()); 54 | System.out.println(Arrays.toString(f.data)); 55 | System.out.println(Arrays.toString(f.asTimeData())); 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/org/jfugue/Measure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Represents a measure marker. This has no bearing on the audio produced, 27 | * but is useful for making music strings more readable, and for listening 28 | * to progress as a song is played. 29 | * 30 | *@author David Koelle 31 | *@version 3.0 32 | */ 33 | public final class Measure implements JFugueElement 34 | { 35 | /** 36 | * Creates a new Measure object, which is simply an indicator 37 | * that a measure line has been parsed in a MusicString 38 | */ 39 | public Measure() 40 | { 41 | } 42 | 43 | /** 44 | * Returns the Music String representing this element. 45 | * For a Measure object, the Music String is | 46 | * @return the Music String for this element 47 | */ 48 | public String getMusicString() 49 | { 50 | return "|"; 51 | } 52 | 53 | /** 54 | * Returns verification string in this format: 55 | * Measure 56 | * @version 4.0 57 | */ 58 | public String getVerifyString() 59 | { 60 | return "Measure"; 61 | } 62 | } -------------------------------------------------------------------------------- /src/org/jfugue/JFugueElement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | import java.io.Serializable; 26 | 27 | /** 28 | * This is the base class for the JFugue elements, including 29 | * Voice, Instrument, Note, Controller, and Tempo. It requires that 30 | * elements be able to return a Music String representation of 31 | * their settings. 32 | * 33 | *@author David Koelle 34 | *@version 2.0 35 | *@version 4.0 - Added getVerifyString() 36 | *@version 4.0.3 - Now extends Serializable 37 | */ 38 | public interface JFugueElement extends Serializable 39 | { 40 | /** 41 | * Returns the Music String representing this element and all of its settings. 42 | * @return the Music String for this element 43 | */ 44 | public String getMusicString(); 45 | 46 | /** 47 | * Returns a verification string, which should contain a String representation 48 | * of all of the aspects of the given element. This should be in the 49 | * following form: 50 | * Thing: key=value, key=value, key=value,... 51 | * For example: 52 | * Note: value=60, duration=0.25 53 | * 54 | * @version 4.0 55 | */ 56 | public String getVerifyString(); 57 | } 58 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/JImage.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui; 2 | import java.awt.Dimension; 3 | import java.awt.Graphics; 4 | import java.awt.Graphics2D; 5 | import java.awt.RenderingHints; 6 | import java.awt.image.BufferedImage; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import javax.imageio.ImageIO; 10 | import javax.swing.JPanel; 11 | 12 | public class JImage extends JPanel { 13 | 14 | /** 15 | * 16 | */ 17 | private static final long serialVersionUID = -7495812403804139277L; 18 | private BufferedImage image; 19 | 20 | public JImage(String path) { 21 | if (path != null) { 22 | try { 23 | image = ImageIO.read(new File(path)); 24 | setPreferredSize(new Dimension(image.getWidth(), image.getHeight())); 25 | } catch (IOException ex) { 26 | System.err.println("Image not found!"); 27 | } 28 | } 29 | this.setOpaque(false); 30 | } 31 | 32 | public static JImage loadFromResources(String path) { 33 | JImage img = new JImage(null); 34 | try { 35 | img.image = ImageIO.read(img.getClass().getClassLoader().getResource(path)); 36 | img.setPreferredSize(new Dimension(img.image.getWidth(), img.image.getHeight())); 37 | } catch (IOException | NullPointerException | IllegalArgumentException e) { 38 | System.err.println("Image resource not found!"); 39 | } 40 | return img; 41 | } 42 | 43 | @Override 44 | protected void paintComponent(Graphics g) { 45 | RenderingHints rh = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 46 | rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 47 | rh.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 48 | rh.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); 49 | rh.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); 50 | rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 51 | 52 | Graphics2D g2d = (Graphics2D) g; 53 | g2d.setRenderingHints(rh); 54 | 55 | super.paintComponent(g2d); 56 | int width = this.getWidth(); 57 | int height = this.getHeight(); 58 | int min = widthtrack changes. 27 | * 28 | *@author David Koelle 29 | *@version 1.0 30 | */ 31 | public final class Voice implements JFugueElement 32 | { 33 | private byte voice; 34 | 35 | /** 36 | * Creates a new Voice object, with the specified voice value. 37 | * @param voice the voice for this object 38 | */ 39 | public Voice(byte voice) 40 | { 41 | setVoice(voice); 42 | } 43 | 44 | /** 45 | * Sets the value of the voice for this object. 46 | * @param tempo the voice for this object 47 | */ 48 | public void setVoice(byte voice) 49 | { 50 | this.voice = voice; 51 | } 52 | 53 | /** 54 | * Returns the voice used in this object 55 | * @return the voice used in this object 56 | */ 57 | public byte getVoice() 58 | { 59 | return voice; 60 | } 61 | 62 | /** 63 | * Returns the Music String representing this element and all of its settings. 64 | * For a Voice object, the Music String is Vvoice 65 | * @return the Music String for this element 66 | */ 67 | public String getMusicString() 68 | { 69 | StringBuffer buffy = new StringBuffer(); 70 | buffy.append("V"); 71 | buffy.append(getVoice()); 72 | return buffy.toString(); 73 | } 74 | 75 | /** 76 | * Returns verification string in this format: 77 | * Voice: voice={#} 78 | * @version 4.0 79 | */ 80 | public String getVerifyString() 81 | { 82 | StringBuffer buffy = new StringBuffer(); 83 | buffy.append("Voice: voice="); 84 | buffy.append(getVoice()); 85 | return buffy.toString(); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/org/jfugue/Time.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Represents a timing value, which can be used to indicate when certain events are played. 27 | * 28 | *@author David Koelle 29 | *@version 3.0 30 | */ 31 | public final class Time implements JFugueElement 32 | { 33 | private long time; 34 | 35 | /** 36 | * Creates a new Time object, with the specified time number. 37 | * @param time the number of the time to use 38 | */ 39 | public Time(long time) 40 | { 41 | setTime(time); 42 | } 43 | 44 | /** 45 | * Sets the value of the time for this object. 46 | * @param time the number of the time to use 47 | */ 48 | public void setTime(long time) 49 | { 50 | this.time = time; 51 | } 52 | 53 | /** 54 | * Returns the time used in this object 55 | * @return the time used in this object 56 | */ 57 | public long getTime() 58 | { 59 | return time; 60 | } 61 | 62 | /** 63 | * Returns the Music String representing this element and all of its settings. 64 | * For a Time object, the Music String is @time 65 | * @return the Music String for this element 66 | */ 67 | public String getMusicString() 68 | { 69 | StringBuffer buffy = new StringBuffer(); 70 | buffy.append("@"); 71 | buffy.append(getTime()); 72 | return buffy.toString(); 73 | } 74 | 75 | /** 76 | * Returns verification string in this format: 77 | * Time: time={#} 78 | * @version 4.0 79 | */ 80 | public String getVerifyString() 81 | { 82 | StringBuffer buffy = new StringBuffer(); 83 | buffy.append("Time: time="); 84 | buffy.append(getTime()); 85 | return buffy.toString(); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/org/jfugue/ChannelPressure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Represents channel pressure changes. 27 | * 28 | *@author David Koelle 29 | *@version 3.0 30 | */ 31 | public final class ChannelPressure implements JFugueElement 32 | { 33 | private byte pressure; 34 | 35 | /** 36 | * Creates a new channel pressure object, with the specified key and pressure values. 37 | * @param key the key to apply pressure to 38 | * @param pressure the pressure to apply 39 | */ 40 | public ChannelPressure(byte pressure) 41 | { 42 | setPressure(pressure); 43 | } 44 | 45 | /** 46 | * Sets the pressure value of this object. 47 | * @param pressure the pressure for this object 48 | */ 49 | public void setPressure(byte pressure) 50 | { 51 | this.pressure = pressure; 52 | } 53 | 54 | /** 55 | * Returns the pressure for this object. 56 | * @return the pressure for this object 57 | */ 58 | public byte getPressure() 59 | { 60 | return this.pressure; 61 | } 62 | 63 | /** 64 | * Returns the Music String representing this element and all of its settings. 65 | * For a channel pressure object, the Music String is +key,pressure 66 | * @return the Music String for this element 67 | */ 68 | public String getMusicString() 69 | { 70 | StringBuffer buffy = new StringBuffer(); 71 | buffy.append("+"); 72 | buffy.append(getPressure()); 73 | return buffy.toString(); 74 | } 75 | 76 | /** 77 | * Returns verification string in this format: 78 | * ChannelPressure: pressure={#} 79 | * @version 4.0 80 | */ 81 | public String getVerifyString() 82 | { 83 | StringBuffer buffy = new StringBuffer(); 84 | buffy.append("ChannelPressure: pressure="); 85 | buffy.append(getPressure()); 86 | return buffy.toString(); 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /src/org/jfugue/Layer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Represents layer changes. A Layer allows multiple sounds to be played at the same 27 | * time on a single track (also known as a voice), without those notes being specified 28 | * as a chord. This is particularly helpful when sing Track 9, the percussion track, 29 | * so multiple percussion sounds can occur at the same time. 30 | * 31 | *@author David Koelle 32 | *@version 3.0 33 | */ 34 | public final class Layer implements JFugueElement 35 | { 36 | private byte layer; 37 | 38 | /** 39 | * Creates a new Layer object, with the specified layer number. 40 | * @param layer the number of the layer to use 41 | */ 42 | public Layer(byte layer) 43 | { 44 | setLayer(layer); 45 | } 46 | 47 | /** 48 | * Sets the value of the layer for this object. 49 | * @param layer the number of the layer to use 50 | */ 51 | public void setLayer(byte layer) 52 | { 53 | this.layer = layer; 54 | } 55 | 56 | /** 57 | * Returns the layer used in this object 58 | * @return the layer used in this object 59 | */ 60 | public byte getLayer() 61 | { 62 | return layer; 63 | } 64 | 65 | /** 66 | * Returns the Music String representing this element and all of its settings. 67 | * For a Layer object, the Music String is Llayer-number 68 | * @return the Music String for this element 69 | */ 70 | public String getMusicString() 71 | { 72 | StringBuffer buffy = new StringBuffer(); 73 | buffy.append("L"); 74 | buffy.append(getLayer()); 75 | return buffy.toString(); 76 | } 77 | 78 | /** 79 | * Returns verification string in this format: 80 | * Layer: layer={#} 81 | * @version 4.0 82 | */ 83 | public String getVerifyString() 84 | { 85 | StringBuffer buffy = new StringBuffer(); 86 | buffy.append("Layer: layer="); 87 | buffy.append(getLayer()); 88 | return buffy.toString(); 89 | } 90 | } -------------------------------------------------------------------------------- /src/org/jfugue/PitchBend.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Represents pitch bend changes. 27 | * 28 | *@author David Koelle 29 | *@version 3.0 30 | */ 31 | public final class PitchBend implements JFugueElement 32 | { 33 | private byte lsb; 34 | private byte msb; 35 | 36 | /** 37 | * Creates a new Pitch Bend object, with the specified tempo value. 38 | * Integer value = msb * 0x80 + lsb (0x80 hex == 128 dec) 39 | * @param lsb the least significant byte for the pitch bend for this object 40 | * @param msb the most significant byte for the pitch bend for this object 41 | */ 42 | public PitchBend(byte lsb, byte msb) 43 | { 44 | setPitchBend(lsb, msb); 45 | } 46 | 47 | /** 48 | * Sets the value of the pitch bend for this object. 49 | * @param tempo the pitch bend for this object 50 | */ 51 | public void setPitchBend(byte lsb, byte msb) 52 | { 53 | this.lsb = lsb; 54 | this.msb = msb; 55 | } 56 | 57 | /** 58 | * Returns the value of the pitch bend for this object. 59 | * @return the value of the pitch bend for this object 60 | */ 61 | public byte[] getBend() 62 | { 63 | return new byte[] { lsb, msb }; 64 | } 65 | 66 | /** 67 | * Returns the Music String representing this element and all of its settings. 68 | * For a PitchBend object, the Music String is &int or &lsb,msb 69 | * @return the Music String for this element 70 | */ 71 | public String getMusicString() 72 | { 73 | StringBuffer buffy = new StringBuffer(); 74 | buffy.append("&"); 75 | buffy.append(getBend()[1] * 0x80 + getBend()[0]); 76 | return buffy.toString(); 77 | } 78 | 79 | /** 80 | * Returns verification string in this format: 81 | * PitchBend: bend={#} 82 | * @version 4.0 83 | */ 84 | public String getVerifyString() 85 | { 86 | StringBuffer buffy = new StringBuffer(); 87 | buffy.append("PitchBend: bend="); 88 | buffy.append(getBend()); 89 | return buffy.toString(); 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/CommandLineManager.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Scanner; 8 | 9 | import javax.sound.midi.InvalidMidiDataException; 10 | 11 | import org.warp.midito3d.music.Music; 12 | import org.warp.midito3d.music.midi.MidiParser; 13 | import org.warp.midito3d.printers.GCodeOutput; 14 | import org.warp.midito3d.printers.Motor; 15 | import org.warp.midito3d.printers.Printer; 16 | import org.warp.midito3d.printers.Printer2Axes; 17 | import org.warp.midito3d.printers.Printer3Axes; 18 | import org.warp.midito3d.printers.Printer4Axes; 19 | import org.warp.midito3d.printers.PrinterZAxis; 20 | 21 | public class CommandLineManager { 22 | public static void execute(String[] args) { 23 | Scanner scanner = null; 24 | try { 25 | GCodeOutput output = new GCodeOutput(args[1]); 26 | boolean motorTest = (args.length >= 5)?Boolean.parseBoolean(args[5-1]):false; 27 | List blacklistedChannels = new ArrayList(); 28 | if (args.length >= 6) { 29 | final String[] blacklistedChannelsStr = args[6-1].split("\\s*,\\s*"); 30 | for (int i = 0; i < blacklistedChannelsStr.length; i++) 31 | blacklistedChannels.add(Integer.parseInt(blacklistedChannelsStr[i])); 32 | } 33 | 34 | float speedMultiplier = Float.parseFloat(args[2]); 35 | float toneMultiplier = Float.parseFloat(args[3]); 36 | 37 | scanner = new Scanner(System.in); 38 | System.out.println("Choose the number of axes to use:\n1:\tOnly axis Z\n3:\tX,Y,Z axes\n4:\tX,Y,Z axes and extruder (WARNING: The extruder must be over 150 degrees to move)"); 39 | int printerAxes = scanner.nextInt(); 40 | 41 | if ((printerAxes != 1) & (printerAxes != 2) & (printerAxes != 3) & (printerAxes != 4)) { 42 | System.err.println("Please pick one of the possible answers!"); 43 | System.exit(1); 44 | } 45 | 46 | Music music = MidiParser.loadFrom(args[0], true); 47 | 48 | music.setBlacklistedChannels(blacklistedChannels); 49 | music.setSpeedMultiplier(speedMultiplier); 50 | music.setToneMultiplier(toneMultiplier); 51 | 52 | Printer printer = null; 53 | 54 | String[] axisName = new String[]{"x", "y", "z", "extruder"}; 55 | if (printerAxes == 1) { 56 | axisName[0] = "z"; 57 | } 58 | Motor[] motors = new Motor[printerAxes]; 59 | for (int m = 0; m < printerAxes; m++) { 60 | System.out.println("Select the speed of the "+axisName[m]+"-axis motor:\n(Usually it's 100 for the x,y,z,extruder axis and 800 for the z axis)"); 61 | motors[m] = new Motor(scanner.nextInt()); 62 | } 63 | 64 | switch (printerAxes) { 65 | case 1: 66 | printer = new PrinterZAxis(motors[0], new PrinterArea(10, 10, 10, 50, 50, 40)); 67 | break; 68 | case 2: 69 | printer = new Printer2Axes(motors[0], motors[1], new PrinterArea(new int[]{10, 10}, new int[]{50, 50})); 70 | break; 71 | case 3: 72 | printer = new Printer3Axes(motors[0], motors[1], motors[2], new PrinterArea(10, 10, 10, 50, 50, 40)); 73 | break; 74 | case 4: 75 | printer = new Printer4Axes(motors[0], motors[1], motors[2], motors[3], new PrinterArea(new int[] {10, 10, 10, 0}, new int[] {50, 50, 40, 100000000})); 76 | break; 77 | } 78 | 79 | Midi23D Midi23D = new Midi23D(printer, music, output, motorTest); 80 | Midi23D.run(); 81 | } catch (InvalidMidiDataException | IOException | URISyntaxException e) { 82 | e.printStackTrace(); 83 | } 84 | if (scanner != null) { 85 | scanner.close(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/org/jfugue/PolyphonicPressure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Represents tempo changes. Tempo is kept for the whole 27 | * song, and is independent of tracks. You may change the 28 | * tempo during a song. 29 | * 30 | *@author David Koelle 31 | *@version 3.0 32 | */ 33 | public final class PolyphonicPressure implements JFugueElement 34 | { 35 | private byte key; 36 | private byte pressure; 37 | 38 | /** 39 | * Creates a new polyphonic pressure object, with the specified key and pressure values. 40 | * @param key the key to apply pressure to 41 | * @param pressure the pressure to apply 42 | */ 43 | public PolyphonicPressure(byte key, byte pressure) 44 | { 45 | setKey(key); 46 | setPressure(pressure); 47 | } 48 | 49 | /** 50 | * Sets the key value of this object. 51 | * @param key the key for this object 52 | */ 53 | public void setKey(byte key) 54 | { 55 | this.key = key; 56 | } 57 | 58 | /** 59 | * Sets the pressure value of this object. 60 | * @param pressure the pressure for this object 61 | */ 62 | public void setPressure(byte pressure) 63 | { 64 | this.pressure = pressure; 65 | } 66 | 67 | /** 68 | * Returns the key for this object. 69 | * @return the key for this object 70 | */ 71 | public byte getKey() 72 | { 73 | return this.key; 74 | } 75 | 76 | /** 77 | * Returns the pressure for this object. 78 | * @return the pressure for this object 79 | */ 80 | public byte getPressure() 81 | { 82 | return this.pressure; 83 | } 84 | 85 | /** 86 | * Returns the Music String representing this element and all of its settings. 87 | * For a polyphonic pressure object, the Music String is *key,pressure 88 | * @return the Music String for this element 89 | */ 90 | public String getMusicString() 91 | { 92 | StringBuffer buffy = new StringBuffer(); 93 | buffy.append("*"); 94 | buffy.append(getKey()); 95 | buffy.append(","); 96 | buffy.append(getPressure()); 97 | return buffy.toString(); 98 | } 99 | 100 | /** 101 | * Returns verification string in this format: 102 | * PolyphonicPressure: key={#}, pressure={#} 103 | * @version 4.0 104 | */ 105 | public String getVerifyString() 106 | { 107 | StringBuffer buffy = new StringBuffer(); 108 | buffy.append("PolyphonicPressure: key="); 109 | buffy.append(getKey()); 110 | buffy.append(", pressure="); 111 | buffy.append(getPressure()); 112 | return buffy.toString(); 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/printers/PrinterNAxes.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.printers; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.Locale; 6 | import org.warp.midito3d.PrinterArea; 7 | 8 | public abstract class PrinterNAxes implements Printer { 9 | 10 | private Motor[] motors; 11 | private final PrinterArea printerArea; 12 | double[] motorsPosition; 13 | double[] motorsDirection; 14 | 15 | PrinterNAxes(Motor[] motors, PrinterArea printerArea) { 16 | this.motors = motors; 17 | this.printerArea = printerArea; 18 | motorsPosition = new double[motors.length]; 19 | motorsDirection = new double[motors.length]; 20 | Arrays.fill(motorsDirection, 1d); 21 | } 22 | 23 | @Override 24 | public final int getMotorsCount() { 25 | return motors.length; 26 | } 27 | 28 | @Override 29 | public void initialize(GCodeOutput po) throws IOException { 30 | po.writeLine("M82"); 31 | po.writeLine("G21"); 32 | po.writeLine("G90"); 33 | po.writeLine("G28 X0 Y0"); 34 | po.writeLine("G0 Z0 F8000"); 35 | po.writeLine("M302 S0 P1"); 36 | } 37 | 38 | @Override 39 | public void wait(GCodeOutput po, double time) throws IOException { 40 | po.writeLine(String.format(Locale.US, "G04 S%.4f", time)); 41 | } 42 | 43 | @Override 44 | public void move(GCodeOutput po, double time, double... speed) throws IOException { 45 | // mm 46 | double[] initialPosition = Arrays.copyOf(motorsPosition, motorsPosition.length); 47 | // mm 48 | double[] finalPosition = Arrays.copyOf(motorsPosition, motorsPosition.length); 49 | 50 | // calculate final position 51 | for (int i = 0; i < initialPosition.length; i++) { 52 | double motorDelta = ((speed[i] / 60d * time) * motorsDirection[i]); 53 | finalPosition[i] += motorDelta; 54 | } 55 | 56 | goToInternal(po, time, initialPosition, finalPosition); 57 | } 58 | 59 | @Override 60 | public void goTo(GCodeOutput po, double time, double... position) throws IOException { 61 | // mm 62 | double[] initialPosition = Arrays.copyOf(motorsPosition, motorsPosition.length); 63 | // mm 64 | double[] finalPosition = Arrays.copyOf(position, motorsPosition.length); 65 | 66 | goToInternal(po, time, initialPosition, finalPosition); 67 | } 68 | 69 | /** 70 | * @param time seconds 71 | * @param initialPosition mm 72 | * @param finalPosition mm 73 | */ 74 | private void goToInternal(GCodeOutput po, double time, double[] initialPosition, double[] finalPosition) throws IOException { 75 | double distance = euclideanDistance(initialPosition, finalPosition); 76 | 77 | // mm/min 78 | double aggregateSpeed = distance / (time / 60d); 79 | 80 | // update global state 81 | for (int i = 0; i < finalPosition.length; i++) { 82 | if (isBiggerThanMax(i, finalPosition[i])) { 83 | motorsDirection[i] = -1d; 84 | } 85 | if (isSmallerThanMin(i, finalPosition[i])) { 86 | motorsDirection[i] = 1d; 87 | } 88 | } 89 | System.arraycopy(finalPosition, 0, motorsPosition, 0, finalPosition.length); 90 | 91 | writeGMove(po, false, aggregateSpeed, finalPosition); 92 | } 93 | 94 | protected abstract void writeGMove(GCodeOutput po, boolean fastMove, double feed, double[] motorsPosition) throws IOException; 95 | 96 | @Override 97 | public void stop(GCodeOutput po) throws IOException { 98 | } 99 | 100 | @Override 101 | public Motor getMotor(int number) { 102 | return motors[number]; 103 | } 104 | 105 | @Override 106 | public boolean isBiggerThanMax(int motor, double val) { 107 | return val > printerArea.max[motor]; 108 | } 109 | 110 | @Override 111 | public boolean isSmallerThanMin(int motor, double val) { 112 | return val < printerArea.min[motor]; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | Midi23D 4 | Midi23D 5 | jar 6 | 0.9.0-BETA 7 | Midi23D 8 | 9 | 1.8 10 | 8 11 | 8 12 | UTF-8 13 | 14 | 15 | 16 | clojars 17 | https://clojars.org/repo/ 18 | 19 | 20 | 21 | 22 | com.github.wendykierp 23 | JTransforms 24 | 3.1 25 | with-dependencies 26 | 27 | 28 | com.googlecode.soundlibs 29 | jlayer 30 | 1.0.1.4 31 | 32 | 33 | 34 | com.jsyn 35 | jsyn 36 | 20170328 37 | 38 | 39 | com.formdev 40 | flatlaf 41 | 3.4.1 42 | 43 | 44 | 45 | src 46 | 47 | 48 | res 49 | 50 | **/*.java 51 | 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-jar-plugin 58 | 59 | ${maven.compiler.source} 60 | ${maven.compiler.target} 61 | ${project.build.sourceEncoding} 62 | 63 | 64 | org.warp.midito3d.Main 65 | true 66 | lib/ 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-assembly-plugin 75 | 2.4.1 76 | 77 | 78 | 79 | jar-with-dependencies 80 | 81 | 82 | 83 | 84 | org.warp.midito3d.Main 85 | 86 | 87 | 88 | 89 | 90 | 91 | make-assembly 92 | 93 | package 94 | 95 | single 96 | 97 | 98 | 99 | 100 | 101 | 102 | http://bit.ly/midi23d 103 | Midi23D is a tool made in Java that converts every note of a .midi music into GCODE instructions to send directly to a 3D printer. 104 | -------------------------------------------------------------------------------- /src/org/jfugue/Tempo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Represents tempo changes. Tempo is kept for the whole 27 | * song, and is independent of tracks. You may change the 28 | * tempo during a song. 29 | * 30 | * As of JFugue 4.0, Tempo represents the Beats Per Minute (BPM). 31 | * In previous versions, Tempo was measured in microseconds per beat, 32 | * which is how MIDI maintains this information. 33 | * (tempo = 60000 / BPM, and BPM = 60000 / tempo) 34 | * 35 | *@author David Koelle 36 | *@version 2.0 37 | *@version 4.0 38 | */ 39 | public final class Tempo implements JFugueElement 40 | { 41 | private int tempo; 42 | 43 | /** 44 | * Creates a new Tempo object, with the specified tempo value (in BPM). 45 | * @param tempo the tempo for this object, in Beats Per Minute 46 | */ 47 | public Tempo(int tempoInBPM) 48 | { 49 | setTempo(tempoInBPM); 50 | } 51 | 52 | /** 53 | * Sets the value of the tempo for this object. 54 | * @param tempo the tempo for this object 55 | */ 56 | public void setTempo(int tempoInBPM) 57 | { 58 | this.tempo = tempoInBPM; 59 | } 60 | 61 | /** 62 | * Returns the value of the tempo for this object. 63 | * @return the value of the tempo for this object 64 | */ 65 | public int getTempo() 66 | { 67 | return tempo; 68 | } 69 | 70 | /** 71 | * Returns the Music String representing this element and all of its settings. 72 | * For a Tempo object, the Music String is Ttempo 73 | * @return the Music String for this element 74 | */ 75 | public String getMusicString() 76 | { 77 | StringBuffer buffy = new StringBuffer(); 78 | buffy.append("T"); 79 | buffy.append(getTempo()); 80 | return buffy.toString(); 81 | } 82 | 83 | /** 84 | * Returns verification string in this format: 85 | * Tempo: tempo={#} 86 | * @version 4.0 87 | */ 88 | public String getVerifyString() 89 | { 90 | StringBuffer buffy = new StringBuffer(); 91 | buffy.append("Tempo: tempo="); 92 | buffy.append(getTempo()); 93 | return buffy.toString(); 94 | } 95 | 96 | public static final int GRAVE = 40; 97 | public static final int LARGO = 45; 98 | public static final int LARGHETTO = 50; 99 | public static final int LENTO = 55; 100 | public static final int ADAGIO = 60; 101 | public static final int ADAGIETTO = 65; 102 | 103 | public static final int ANDANTE = 70; 104 | public static final int ANDANTINO = 80; 105 | public static final int MODERATO = 95; 106 | public static final int ALLEGRETTO = 110; 107 | 108 | public static final int ALLEGRO = 120; 109 | public static final int VIVACE = 145; 110 | public static final int PRESTO = 180; 111 | public static final int PRETISSIMO = 220; 112 | 113 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/ModernDialog.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui; 2 | 3 | import java.awt.FileDialog; 4 | import java.io.File; 5 | 6 | import javax.swing.JFileChooser; 7 | import javax.swing.JFrame; 8 | import javax.swing.SwingUtilities; 9 | import javax.swing.filechooser.FileFilter; 10 | 11 | /** 12 | * Modern dialog 13 | * @version 1.0.0 14 | * @author Andrea Cavalli 15 | * 16 | */ 17 | public class ModernDialog { 18 | 19 | private boolean awtChooser; 20 | private JFileChooser jchooser; 21 | private ModernExtensionFilter[] awtChooserFilters; 22 | private String awtChooserTitle; 23 | 24 | public ModernDialog() { 25 | try { 26 | awtChooser = true; 27 | } catch (Throwable ex) { 28 | jchooser = new JFileChooser(); 29 | } 30 | } 31 | 32 | public static void runLater(Runnable r) { 33 | SwingUtilities.invokeLater(r); 34 | } 35 | 36 | public void setTitle(String text) { 37 | if (awtChooser) { 38 | awtChooserTitle = text; 39 | } else { 40 | jchooser.setDialogTitle(text); 41 | } 42 | } 43 | 44 | public void setExtensions(final ModernExtensionFilter... filters) { 45 | if (awtChooser) { 46 | awtChooserFilters = filters; 47 | } else { 48 | jchooser.setFileFilter(new FileFilter() { 49 | 50 | @Override 51 | public String getDescription() { 52 | return filters[0].name; 53 | } 54 | 55 | @Override 56 | public boolean accept(File file) { 57 | if (file.isDirectory()) { 58 | return true; 59 | } else { 60 | String path = file.getAbsolutePath().toLowerCase(); 61 | for (int j = 0; j < filters.length; j++) { 62 | for (int i = 0, n = filters[j].filters.length; i < n; i++) { 63 | String extension = filters[j].filters[i].substring(2); 64 | if ((path.endsWith(extension) && (path.charAt(path.length() 65 | - extension.length() - 1)) == '.')) { 66 | return true; 67 | } 68 | } 69 | } 70 | } 71 | return false; 72 | } 73 | }); 74 | } 75 | } 76 | 77 | private FileDialog createAwtChooser(JFrame parent, boolean load) { 78 | FileDialog awtChooser = new java.awt.FileDialog(parent, awtChooserTitle, load ? FileDialog.LOAD : FileDialog.SAVE); 79 | awtChooser.setMultipleMode(false); 80 | awtChooser.setFilenameFilter((dir, name) -> { 81 | for (ModernExtensionFilter filter : awtChooserFilters) { 82 | for (int i = 0, n = filter.filters.length; i < n; i++) { 83 | String extension = filter.filters[i].substring(2); 84 | if ((name.endsWith(extension) && (name.charAt(name.length() - extension.length() - 1)) == '.')) { 85 | return true; 86 | } 87 | } 88 | } 89 | return false; 90 | }); 91 | return awtChooser; 92 | } 93 | 94 | public File show(JFrame parent) { 95 | if (awtChooser) { 96 | FileDialog awtChooser = createAwtChooser(parent, true); 97 | awtChooser.setLocationRelativeTo(null); 98 | awtChooser.setVisible(true); 99 | File[] result = awtChooser.getFiles(); 100 | if (result.length == 0) { 101 | return null; 102 | } else { 103 | return result[0]; 104 | } 105 | } else { 106 | if (jchooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { 107 | return jchooser.getSelectedFile(); 108 | } else { 109 | return null; 110 | } 111 | } 112 | } 113 | 114 | public File showSaveDialog(JFrame parent) { 115 | if (awtChooser) { 116 | FileDialog awtChooser = createAwtChooser(parent, false); 117 | awtChooser.setVisible(true); 118 | File[] result = awtChooser.getFiles(); 119 | if (result.length == 0) { 120 | return null; 121 | } else { 122 | return result[0]; 123 | } 124 | } else { 125 | if (jchooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) { 126 | return jchooser.getSelectedFile(); 127 | } else { 128 | return null; 129 | } 130 | } 131 | } 132 | 133 | public static class ModernExtensionFilter { 134 | public String[] filters; 135 | public String name; 136 | 137 | public ModernExtensionFilter(String name, String... filters){ 138 | this.filters = filters; 139 | this.name = name; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/FFT.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | /****************************************************************************** 3 | * Compilation: javac FFT.java 4 | * Execution: java FFT n 5 | * Dependencies: Complex.java 6 | * 7 | * Compute the FFT and inverse FFT of a length n complex sequence. 8 | * Bare bones implementation that runs in O(n log n) time. Our goal 9 | * is to optimize the clarity of the code, rather than performance. 10 | * 11 | * Limitations 12 | * ----------- 13 | * - assumes n is a power of 2 14 | * 15 | * - not the most memory efficient algorithm (because it uses 16 | * an object type for representing complex numbers and because 17 | * it re-allocates memory for the subarray, instead of doing 18 | * in-place or reusing a single temporary array) 19 | * 20 | ******************************************************************************/ 21 | 22 | public class FFT { 23 | 24 | // compute the FFT of x[], assuming its length is a power of 2 25 | public static Complex[] fft(Complex[] x) { 26 | int n = x.length; 27 | 28 | // base case 29 | if (n == 1) return new Complex[] { x[0] }; 30 | 31 | // radix 2 Cooley-Tukey FFT 32 | if (n % 2 != 0) { throw new RuntimeException("n is not a power of 2"); } 33 | 34 | // fft of even terms 35 | Complex[] even = new Complex[n/2]; 36 | for (int k = 0; k < n/2; k++) { 37 | even[k] = x[2*k]; 38 | } 39 | Complex[] q = fft(even); 40 | 41 | // fft of odd terms 42 | Complex[] odd = even; // reuse the array 43 | for (int k = 0; k < n/2; k++) { 44 | odd[k] = x[2*k + 1]; 45 | } 46 | Complex[] r = fft(odd); 47 | 48 | // combine 49 | Complex[] y = new Complex[n]; 50 | for (int k = 0; k < n/2; k++) { 51 | double kth = -2 * k * Math.PI / n; 52 | Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); 53 | y[k] = q[k].plus(wk.times(r[k])); 54 | y[k + n/2] = q[k].minus(wk.times(r[k])); 55 | } 56 | return y; 57 | } 58 | 59 | 60 | // compute the inverse FFT of x[], assuming its length is a power of 2 61 | public static Complex[] ifft(Complex[] x) { 62 | int n = x.length; 63 | Complex[] y = new Complex[n]; 64 | 65 | // take conjugate 66 | for (int i = 0; i < n; i++) { 67 | y[i] = x[i].conjugate(); 68 | } 69 | 70 | // compute forward FFT 71 | y = fft(y); 72 | 73 | // take conjugate again 74 | for (int i = 0; i < n; i++) { 75 | y[i] = y[i].conjugate(); 76 | } 77 | 78 | // divide by n 79 | for (int i = 0; i < n; i++) { 80 | y[i] = y[i].scale(1.0 / n); 81 | } 82 | 83 | return y; 84 | 85 | } 86 | 87 | // compute the circular convolution of x and y 88 | public static Complex[] cconvolve(Complex[] x, Complex[] y) { 89 | 90 | // should probably pad x and y with 0s so that they have same length 91 | // and are powers of 2 92 | if (x.length != y.length) { throw new RuntimeException("Dimensions don't agree"); } 93 | 94 | int n = x.length; 95 | 96 | // compute FFT of each sequence 97 | Complex[] a = fft(x); 98 | Complex[] b = fft(y); 99 | 100 | // point-wise multiply 101 | Complex[] c = new Complex[n]; 102 | for (int i = 0; i < n; i++) { 103 | c[i] = a[i].times(b[i]); 104 | } 105 | 106 | // compute inverse FFT 107 | return ifft(c); 108 | } 109 | 110 | 111 | // compute the linear convolution of x and y 112 | public static Complex[] convolve(Complex[] x, Complex[] y) { 113 | Complex ZERO = new Complex(0, 0); 114 | 115 | Complex[] a = new Complex[2*x.length]; 116 | for (int i = 0; i < x.length; i++) a[i] = x[i]; 117 | for (int i = x.length; i < 2*x.length; i++) a[i] = ZERO; 118 | 119 | Complex[] b = new Complex[2*y.length]; 120 | for (int i = 0; i < y.length; i++) b[i] = y[i]; 121 | for (int i = y.length; i < 2*y.length; i++) b[i] = ZERO; 122 | 123 | return cconvolve(a, b); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/FilenameUtils.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d; 2 | 3 | /* 4 | * Licensed to the Apache Software Foundation (ASF) under one or more 5 | * contributor license agreements. See the NOTICE file distributed with 6 | * this work for additional information regarding copyright ownership. 7 | * The ASF licenses this file to You under the Apache License, Version 2.0 8 | * (the "License"); you may not use this file except in compliance with 9 | * the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | public class FilenameUtils { 20 | 21 | /** 22 | * The extension separator character. 23 | */ 24 | private static final char EXTENSION_SEPARATOR = '.'; 25 | 26 | /** 27 | * The Unix separator character. 28 | */ 29 | private static final char UNIX_SEPARATOR = '/'; 30 | 31 | /** 32 | * The Windows separator character. 33 | */ 34 | private static final char WINDOWS_SEPARATOR = '\\'; 35 | 36 | /** 37 | * Gets the extension of a filename. 38 | *

39 | * This method returns the textual part of the filename after the last dot. 40 | * There must be no directory separator after the dot. 41 | *

 42 |      * foo.txt      --> "txt"
 43 |      * a/b/c.jpg    --> "jpg"
 44 |      * a/b.txt/c    --> ""
 45 |      * a/b/c        --> ""
 46 |      * 
47 | *

48 | * The output will be the same irrespective of the machine that the code is running on. 49 | * 50 | * @param filename the filename to retrieve the extension of. 51 | * @return the extension of the file or an empty string if none exists. 52 | */ 53 | public static String getExtension(String filename) { 54 | if (filename == null) { 55 | return null; 56 | } 57 | int index = indexOfExtension(filename); 58 | if (index == -1) { 59 | return ""; 60 | } else { 61 | return filename.substring(index + 1); 62 | } 63 | } 64 | 65 | /** 66 | * Returns the index of the last extension separator character, which is a dot. 67 | *

68 | * This method also checks that there is no directory separator after the last dot. 69 | * To do this it uses {@link #indexOfLastSeparator(String)} which will 70 | * handle a file in either Unix or Windows format. 71 | *

72 | * The output will be the same irrespective of the machine that the code is running on. 73 | * 74 | * @param filename the filename to find the last path separator in, null returns -1 75 | * @return the index of the last separator character, or -1 if there 76 | * is no such character 77 | */ 78 | public static int indexOfExtension(String filename) { 79 | if (filename == null) { 80 | return -1; 81 | } 82 | int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR); 83 | int lastSeparator = indexOfLastSeparator(filename); 84 | return (lastSeparator > extensionPos ? -1 : extensionPos); 85 | } 86 | 87 | /** 88 | * Returns the index of the last directory separator character. 89 | *

90 | * This method will handle a file in either Unix or Windows format. 91 | * The position of the last forward or backslash is returned. 92 | *

93 | * The output will be the same irrespective of the machine that the code is running on. 94 | * 95 | * @param filename the filename to find the last path separator in, null returns -1 96 | * @return the index of the last separator character, or -1 if there 97 | * is no such character 98 | */ 99 | public static int indexOfLastSeparator(String filename) { 100 | if (filename == null) { 101 | return -1; 102 | } 103 | int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR); 104 | int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR); 105 | return Math.max(lastUnixPos, lastWindowsPos); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/Clip.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.logging.Logger; 10 | import javax.sound.sampled.AudioFormat; 11 | import javax.sound.sampled.AudioInputStream; 12 | import javax.sound.sampled.AudioSystem; 13 | import javax.sound.sampled.UnsupportedAudioFileException; 14 | 15 | public class Clip { 16 | private static final Logger logger = Logger.getLogger(Clip.class.getName()); 17 | private static final AudioFormat AUDIO_FORMAT = new AudioFormat(44100.0f, 16, 1, true, true); 18 | private final List frames = new ArrayList(); 19 | private int frameSize = 1024; 20 | private int overlap = 2; 21 | private double spectralScale = 10000.0; 22 | 23 | public Clip(File file) throws UnsupportedAudioFileException, IOException { 24 | int n; 25 | WindowFunction windowFunc = new NullWindowFunction();//new VorbisWindowFunction(this.frameSize); 26 | AudioFormat desiredFormat = AUDIO_FORMAT; 27 | BufferedInputStream in = new BufferedInputStream(AudioSystem.getAudioInputStream(desiredFormat, AudioSystem.getAudioInputStream(file))); 28 | byte[] buf = new byte[this.frameSize * 2]; 29 | in.mark(buf.length * 2); 30 | while ((n = in.read(buf)) != -1) { 31 | logger.fine("Read " + n + " bytes"); 32 | double[] samples = new double[this.frameSize]; 33 | for (int i = 0; i < this.frameSize; ++i) { 34 | byte hi = buf[2 * i]; 35 | int low = buf[2 * i + 1] & 255; 36 | int sampVal = hi << 8 | low; 37 | samples[i] = (double)sampVal / this.spectralScale; 38 | } 39 | this.frames.add(new Frame(samples, windowFunc)); 40 | in.reset(); 41 | in.skip(this.frameSize * 2 / this.overlap); 42 | in.mark(buf.length * 2); 43 | } 44 | logger.info(String.format("Read %d frames from %s (%d bytes)\n", this.frames.size(), file.getAbsolutePath(), this.frames.size() * buf.length)); 45 | } 46 | 47 | public int getFrameTimeSamples() { 48 | return this.frameSize; 49 | } 50 | 51 | public int getFrameFreqSamples() { 52 | return this.frameSize; 53 | } 54 | 55 | public double getSpectralScale() { 56 | return this.spectralScale; 57 | } 58 | 59 | public int getFrameCount() { 60 | return this.frames.size(); 61 | } 62 | 63 | public Frame getFrame(int i) { 64 | return this.frames.get(i); 65 | } 66 | 67 | public AudioInputStream getAudio() { 68 | InputStream audioData = new InputStream(){ 69 | int nextFrame; 70 | OverlapBuffer overlapBuffer; 71 | int currentSample; 72 | boolean currentByteHigh; 73 | int emptyFrameCount; 74 | 75 | public int available() throws IOException { 76 | return Integer.MAX_VALUE; 77 | } 78 | 79 | public int read() throws IOException { 80 | if (this.overlapBuffer.needsNewFrame()) { 81 | if (this.nextFrame < Clip.this.frames.size()) { 82 | Frame f = (Frame)Clip.this.frames.get(this.nextFrame++); 83 | this.overlapBuffer.addFrame(f.asTimeData()); 84 | } else { 85 | this.overlapBuffer.addEmptyFrame(); 86 | ++this.emptyFrameCount; 87 | } 88 | } 89 | if (this.emptyFrameCount >= Clip.this.overlap) { 90 | return -1; 91 | } 92 | if (this.currentByteHigh) { 93 | this.currentSample = (int)(this.overlapBuffer.next() * Clip.this.spectralScale); 94 | this.currentByteHigh = false; 95 | return this.currentSample >> 8 & 255; 96 | } 97 | this.currentByteHigh = true; 98 | return this.currentSample & 255; 99 | } 100 | }; 101 | int length = this.getFrameCount() * this.getFrameTimeSamples() * (AUDIO_FORMAT.getSampleSizeInBits() / 8) / this.overlap; 102 | return new AudioInputStream(audioData, AUDIO_FORMAT, length); 103 | } 104 | 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/org/jfugue/TimeEventManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | import java.util.ArrayList; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | 30 | import javax.sound.midi.MidiEvent; 31 | import javax.sound.midi.Sequence; 32 | import javax.sound.midi.Track; 33 | 34 | /** 35 | * Takes the events in a MIDI sequence and places them into a time-based 36 | * map. This is done so the events can be played back in order of when 37 | * the events occur, regardless of the tracks they happen to be in. This is 38 | * useful when sending events to an external device, or any occasion 39 | * when iterating through the tracks is not useful because the tracks would be 40 | * played sequentially rather than in parallel. 41 | * 42 | * @author David Koelle 43 | * @version 3.0 44 | */ 45 | public final class TimeEventManager 46 | { 47 | public static final long sortSequenceByTimestamp(Sequence sequence, Map> timeMap) 48 | { 49 | // Keep track of how long the sequence is 50 | long longestTime = 0; 51 | 52 | // Iterate through the tracks, and store the events into our time map 53 | Track[] tracks = sequence.getTracks(); 54 | for (int i=0; i < tracks.length; i++) 55 | { 56 | for (int e=0; e < tracks[i].size(); e++) 57 | { 58 | // Get MIDI message and time data from event 59 | MidiEvent event = tracks[i].get(e); 60 | long timestamp = event.getTick(); 61 | 62 | // Put the MIDI message into the time map 63 | List list = null; 64 | if ((list = (ArrayList)timeMap.get(timestamp)) == null) 65 | { 66 | // Add a new list to the map if one doesn't already exist 67 | // for the timestamp in question 68 | list = new ArrayList(); 69 | timeMap.put(timestamp, list); 70 | } 71 | list.add(event); 72 | 73 | // Update the longest time known, if required 74 | if (timestamp > longestTime) 75 | { 76 | longestTime = timestamp; 77 | } 78 | } 79 | } 80 | 81 | return longestTime; 82 | } 83 | 84 | /** 85 | * Returns the events from this sequence in temporal order. This is 86 | * done in a two step process: 87 | * 1. mapSequence() populates timeMap. Each timestamp key in timeMap is mapped to 88 | * a List of events that take place at that time 89 | * 2. A list of all events from all timestamps is created and returned 90 | * @return The events from the sequence, in temporal order 91 | */ 92 | public static final List getAllEventsSortedByTimestamp(Sequence sequence) 93 | { 94 | Map> timeMap = new HashMap>(); 95 | long longestTime = sortSequenceByTimestamp(sequence, timeMap); 96 | 97 | List totalList = new ArrayList(); 98 | 99 | for (long l=0; l < longestTime; l++) 100 | { 101 | Long key = new Long(l); 102 | if (timeMap.containsKey(key)) 103 | { 104 | List list = (List)timeMap.get(key); 105 | totalList.addAll(list); 106 | } 107 | } 108 | 109 | return totalList; 110 | } 111 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/Mp3Music.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | import java.io.PrintStream; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.util.List; 8 | 9 | import javax.sound.sampled.AudioInputStream; 10 | 11 | import org.warp.midito3d.music.DoneListener; 12 | import org.warp.midito3d.music.Music; 13 | import org.warp.midito3d.music.Note; 14 | 15 | import javazoom.jl.decoder.Bitstream; 16 | 17 | public class Mp3Music implements Music { 18 | 19 | private double[][] freqs; 20 | 21 | private double tempo = 500000; 22 | private double speedMultiplier = 1.0d; 23 | private float toneMultiplier = 1.0f; 24 | 25 | private long currentTick = -1; 26 | 27 | private int channelsCount = 4; 28 | 29 | private PrintStream out; 30 | private PrintStream err; 31 | 32 | private boolean errored = false; 33 | 34 | private float samplesPerSecond; 35 | 36 | private double currentNotes[]; 37 | 38 | Mp3Music(double[][] freqs, float samplesPerSecond, boolean debug) { 39 | setDebugOutput(debug); 40 | this.freqs = freqs; 41 | this.channelsCount = freqs.length; 42 | this.samplesPerSecond = samplesPerSecond; //52f/12f; 43 | } 44 | 45 | @Override 46 | public void setOutputChannelsCount(int i) { 47 | this.err.print("Not implemented function: setOutputChannelsCount("+i+")"); 48 | } 49 | 50 | @Override 51 | public void reanalyze(DoneListener l) { 52 | try { 53 | 54 | // if (mus.available()) { 55 | // currentNote = mus.read(); 56 | // mus.getFormat().getFrameSize() 57 | // System.out.println(mus.getCustomTag()); 58 | // System.exit(1); 59 | // } 60 | } catch (Exception ex) { 61 | ex.printStackTrace(); 62 | errored = true; 63 | } 64 | 65 | currentTick = -1; 66 | } 67 | 68 | @Override 69 | public boolean hasNext() { 70 | if (errored) { 71 | return false; 72 | } 73 | return currentTick + 1 < freqs[0].length; 74 | } 75 | 76 | @Override 77 | public void findNext() { 78 | int delta = 1; 79 | boolean allEquals = false; 80 | do { 81 | currentNotes = new double[channelsCount]; 82 | for (int i = 0; i < channelsCount; i++) { 83 | currentNotes[i] = this.freqs[i][(int) currentTick+delta]; 84 | } 85 | delta++; 86 | if (currentTick+delta >= this.freqs[0].length) { 87 | break; 88 | } 89 | allEquals = true; 90 | for (int i = 0; i < this.channelsCount; i++) { 91 | if (currentNotes[i] != this.freqs[i][(int) currentTick+delta]) { 92 | allEquals = false; 93 | break; 94 | } 95 | } 96 | } while(allEquals); 97 | currentTick += delta; 98 | } 99 | 100 | @Override 101 | public long getCurrentTick() { 102 | return currentTick; 103 | } 104 | 105 | @Override 106 | public double getDivision() { 107 | return samplesPerSecond; 108 | } 109 | 110 | @Override 111 | public double getCurrentTempo() { 112 | return 1;//0.00037409711d;//12.69d/299d;//15d/856d; 113 | } 114 | 115 | @Override 116 | public Note getCurrentNote(int channel) { 117 | return new Mp3Note(currentNotes[channel], 69d, currentTick); 118 | } 119 | 120 | @Override 121 | public double getChannelPitch(int channel) { 122 | // TODO Auto-generated method stub 123 | return 1; 124 | } 125 | 126 | @Override 127 | public double getSpeedMultiplier() { 128 | // TODO Auto-generated method stub 129 | return 1; 130 | } 131 | 132 | @Override 133 | public void setSpeedMultiplier(double f) { 134 | // TODO Auto-generated method stub 135 | 136 | } 137 | 138 | @Override 139 | public float getToneMultiplier() { 140 | // TODO Auto-generated method stub 141 | return 1; 142 | } 143 | 144 | @Override 145 | public void setToneMultiplier(float f) { 146 | // TODO Auto-generated method stub 147 | 148 | } 149 | 150 | @Override 151 | public void setBlacklistedChannels(List blacklistedChannels) { 152 | this.err.print("Not implemented function: setBlacklistedChannels("+blacklistedChannels+")"); 153 | } 154 | 155 | @Override 156 | public void setDebugOutput(boolean debug) { 157 | if (debug) { 158 | this.out = System.out; 159 | this.err = System.err; 160 | } else { 161 | this.out = new PrintStream(new OutputStream() { 162 | @Override 163 | public void write(int b) throws IOException { 164 | 165 | } 166 | }); 167 | this.err = this.out; 168 | } 169 | } 170 | 171 | @Override 172 | public long getLength() { 173 | if (this.freqs.length > 0) { 174 | return this.freqs[0].length; 175 | } else { 176 | return 0; 177 | } 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/org/jfugue/KeySignature.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Represents key signature changes. Key signatures are kept for the 27 | * whole song, independent of tracks. You may change the key signature 28 | * during a song. The Player will automatically adjust note values based 29 | * on the current key signature - for example, in an F Major key signature, 30 | * B will be converted to B-flat automatically, unless the B is noted as 31 | * a natural B (i.e., "Bn") 32 | * 33 | *@author David Koelle 34 | *@version 3.0 35 | */ 36 | public final class KeySignature implements JFugueElement 37 | { 38 | private byte keySig; 39 | private byte scale; 40 | 41 | /** 42 | * Creates a new key signature object, with the specified tempo value. 43 | * @param keySig the key signature for this object, -7 to +7 - see MIDI specification for more details 44 | * @param scale whether this is a major (0) or minor (1) key signature 45 | */ 46 | public KeySignature(byte keySig, byte scale) 47 | { 48 | setKeySig(keySig); 49 | setScale(scale); 50 | } 51 | 52 | /** 53 | * Sets the key signature, from -7 to +7, for this object. 54 | * See the MIDI specification for more details 55 | * @param keySig the key signature for this object 56 | */ 57 | public void setKeySig(byte keySig) 58 | { 59 | this.keySig = keySig; 60 | } 61 | 62 | /** 63 | * Sets the scale - 0 for major, 1 for minor. 64 | * See the MIDI specification for more details 65 | * @param scale the scale for this object 66 | */ 67 | public void setScale(byte scale) 68 | { 69 | this.scale = scale; 70 | } 71 | 72 | /** 73 | * Returns the key signature for this object. 74 | * @return the key signature for this object 75 | */ 76 | public byte getKeySig() 77 | { 78 | return this.keySig; 79 | } 80 | 81 | /** 82 | * Returns the scale for this object. 83 | * @return the scale for this object 84 | */ 85 | public byte getScale() 86 | { 87 | return this.scale; 88 | } 89 | 90 | /** 91 | * Returns the Music String representing this element and all of its settings. 92 | * For a key signature object, the Music String is Kkeysig, 93 | * where 'keysig' is a root note followed by 'maj' or 'min' (i.e., Cbmaj for C-flat major) 94 | * @return the Music String for this element 95 | */ 96 | public String getMusicString() 97 | { 98 | StringBuilder keySigSB = new StringBuilder(); 99 | keySigSB.append("K"); 100 | if (scale == 0) { 101 | keySigSB.append(majorSigs[getKeySig()+7]); 102 | keySigSB.append("maj"); 103 | } else { 104 | keySigSB.append(minorSigs[getKeySig()+7]); 105 | keySigSB.append("min"); 106 | } 107 | return keySigSB.toString(); 108 | } 109 | 110 | /** 111 | * Returns verification string in this format: 112 | * KeySig: keySig={#}, scale={#} 113 | * @version 4.0 114 | */ 115 | public String getVerifyString() 116 | { 117 | StringBuffer buffy = new StringBuffer(); 118 | buffy.append("KeySig: keySig="); 119 | buffy.append(getKeySig()); 120 | buffy.append(", scale="); 121 | buffy.append(getScale()); 122 | return buffy.toString(); 123 | } 124 | 125 | private static final String[] majorSigs = new String[] { "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#" }; 126 | private static final String[] minorSigs = new String[] { "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "D#", "A#" }; 127 | } -------------------------------------------------------------------------------- /src/org/jfugue/ParserListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | import java.util.EventListener; 26 | 27 | /** 28 | * Classes that implement ParserListener and add themselves as listeners 29 | * to a Parser object will receive events when 30 | * the Parser inteprets tokens from a Music String. 31 | * @see MusicStringParser 32 | * 33 | *@author David Koelle 34 | *@version 3.0 35 | */ 36 | public interface ParserListener extends EventListener 37 | { 38 | /** 39 | * Called when the parser encounters a voice event. 40 | * @param voice the event that has been parsed 41 | * @see Voice 42 | */ 43 | public void voiceEvent(Voice voice); 44 | 45 | /** 46 | * Called when the parser encounters a tempo event. 47 | * @param tempo the event that has been parsed 48 | * @see Tempo 49 | */ 50 | public void tempoEvent(Tempo tempo); 51 | 52 | /** 53 | * Called when the parser encounters an instrument event. 54 | * @param instrument the event that has been parsed 55 | * @see Instrument 56 | */ 57 | public void instrumentEvent(Instrument instrument); 58 | 59 | /** 60 | * Called when the parser encounters a layer event. 61 | * @param layer the event that has been parsed 62 | * @see Layer 63 | */ 64 | public void layerEvent(Layer layer); 65 | 66 | /** 67 | * Called when the parser encounters a measure event. 68 | * @param measure the event that has been parsed 69 | * @see Measure 70 | */ 71 | public void measureEvent(Measure measure); 72 | 73 | /** 74 | * Called when the parser encounters a time event. 75 | * @param time the event that has been parsed 76 | * @see Time 77 | */ 78 | public void timeEvent(Time time); 79 | 80 | /** 81 | * Called when the parser encounters a key signature event. 82 | * @param time the event that has been parsed 83 | * @see KeySignature 84 | */ 85 | public void keySignatureEvent(KeySignature keySig); 86 | 87 | /** 88 | * Called when the parser encounters a controller event. 89 | * @param controller the event that has been parsed 90 | */ 91 | public void controllerEvent(Controller controller); 92 | 93 | /** 94 | * Called when the parser encounters a channel pressure event. 95 | * @param channelPressure the event that has been parsed 96 | * @see ChannelPressure 97 | */ 98 | public void channelPressureEvent(ChannelPressure channelPressure); 99 | 100 | /** 101 | * Called when the parser encounters a polyphonic pressure event. 102 | * @param polyphonicPressure the event that has been parsed 103 | * @see PolyphonicPressure 104 | */ 105 | public void polyphonicPressureEvent(PolyphonicPressure polyphonicPressure); 106 | 107 | /** 108 | * Called when the parser encounters a pitch bend event. 109 | * @param pitchBend the event that has been parsed 110 | * @see PitchBend 111 | */ 112 | public void pitchBendEvent(PitchBend pitchBend); 113 | 114 | /** 115 | * Called when the parser encounters an initial note event. 116 | * @param note the event that has been parsed 117 | * @see Note 118 | */ 119 | public void noteEvent(Note note); 120 | 121 | /** 122 | * Called when the parser encounters a sequential note event. 123 | * @param note the event that has been parsed 124 | * @see Note 125 | */ 126 | public void sequentialNoteEvent(Note note); 127 | 128 | /** 129 | * Called when the parser encounters a parallel note event. 130 | * @param note the event that has been parsed 131 | * @see Note 132 | */ 133 | public void parallelNoteEvent(Note note); 134 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/InputSongPanel.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui; 2 | 3 | import java.awt.Color; 4 | import java.awt.Dimension; 5 | import java.awt.Font; 6 | import java.awt.GridBagConstraints; 7 | import java.awt.GridBagLayout; 8 | import java.awt.Insets; 9 | import java.io.File; 10 | 11 | import javax.swing.BorderFactory; 12 | import javax.swing.JLabel; 13 | import javax.swing.JSpinner; 14 | import javax.swing.SpinnerNumberModel; 15 | import javax.swing.SwingConstants; 16 | import javax.swing.event.ChangeEvent; 17 | 18 | import org.warp.midito3d.music.Music; 19 | 20 | public class InputSongPanel extends SongPanel { 21 | 22 | private static final long serialVersionUID = 4440013224587289302L; 23 | 24 | public final JImage songIcon; 25 | public final JLabel description; 26 | 27 | public InputSongPanel(File fileName, Music music) { 28 | 29 | GridBagConstraints c = new GridBagConstraints(); 30 | 31 | super.setLayout(new GridBagLayout()); 32 | 33 | this.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 34 | this.setBackground(Color.white); 35 | 36 | songIcon = JImage.loadFromResources("cd-icon.png"); 37 | songIcon.setMinimumSize(new Dimension(64, 64)); 38 | songIcon.setPreferredSize(new Dimension(64, 64)); 39 | songIcon.setMaximumSize(new Dimension(64, 64)); 40 | c.insets = new Insets(5,5,5,5); 41 | c.anchor = GridBagConstraints.NORTHWEST; 42 | c.fill = GridBagConstraints.BOTH; 43 | c.gridx = 0; 44 | c.gridy = 0; 45 | c.gridwidth = 1; 46 | c.gridheight = 2; 47 | c.weightx = 0; 48 | c.weighty = 0; 49 | add(songIcon, c); 50 | description = new JLabel(fileName.getName()); 51 | description.setMinimumSize(new Dimension(10, 32-5-5)); 52 | description.setMaximumSize(new Dimension(999999, 32-5-5)); 53 | description.setVerticalAlignment(SwingConstants.BOTTOM); 54 | c.insets = new Insets(5,5,5,5); 55 | c.anchor = GridBagConstraints.SOUTHWEST; 56 | c.fill = GridBagConstraints.BOTH; 57 | c.gridx = 1; 58 | c.gridy = 0; 59 | c.gridwidth = 2; 60 | c.gridheight = 1; 61 | c.weightx = 1; 62 | c.weighty = 0; 63 | add(description, c); 64 | c.insets = new Insets(5,5,5,5); 65 | c.anchor = GridBagConstraints.NORTHWEST; 66 | c.fill = GridBagConstraints.BOTH; 67 | c.gridx = 1; 68 | c.gridy = 1; 69 | c.gridwidth = 2; 70 | c.gridheight = 1; 71 | c.weightx = 0; 72 | c.weighty = 0; 73 | add(new JLabel(fileName.toString()), c); 74 | JLabel label = new JLabel("Song speed"); 75 | label.setMinimumSize(new Dimension(130, 20)); 76 | c.insets = new Insets(5,5,5,5); 77 | c.anchor = GridBagConstraints.NORTHWEST; 78 | c.fill = GridBagConstraints.BOTH; 79 | c.gridx = 0; 80 | c.gridy = 2; 81 | c.gridwidth = 2; 82 | c.gridheight = 1; 83 | c.weightx = 0; 84 | c.weighty = 0; 85 | add(label, c); 86 | final JSpinner speedAdj = new JSpinner(new SpinnerNumberModel(1, 0.0001, 10000, 0.1)); 87 | speedAdj.setMinimumSize(new Dimension(75, 20)); 88 | speedAdj.setPreferredSize(new Dimension(75, 20)); 89 | speedAdj.setMaximumSize(new Dimension(75, 20)); 90 | speedAdj.addChangeListener((ChangeEvent e)->{ 91 | music.setSpeedMultiplier((float)((double)speedAdj.getValue())); 92 | }); 93 | c.insets = new Insets(5,5,5,5); 94 | c.anchor = GridBagConstraints.NORTHEAST; 95 | c.fill = GridBagConstraints.VERTICAL; 96 | c.gridx = 2; 97 | c.gridy = 2; 98 | c.gridwidth = 1; 99 | c.gridheight = 1; 100 | c.weightx = 0; 101 | c.weighty = 0; 102 | add(speedAdj, c); 103 | label = new JLabel("Tone multiplier"); 104 | label.setMinimumSize(new Dimension(130, 20)); 105 | c.insets = new Insets(5,5,5,5); 106 | c.anchor = GridBagConstraints.NORTHWEST; 107 | c.fill = GridBagConstraints.BOTH; 108 | c.gridx = 0; 109 | c.gridy = 3; 110 | c.gridwidth = 2; 111 | c.gridheight = 1; 112 | c.weightx = 0; 113 | c.weighty = 0; 114 | add(label, c); 115 | final JSpinner toneAdj = new JSpinner(new SpinnerNumberModel(1, 0.01, 100, 0.25)); 116 | toneAdj.setMinimumSize(new Dimension(75, 20)); 117 | toneAdj.setPreferredSize(new Dimension(75, 20)); 118 | toneAdj.setMaximumSize(new Dimension(75, 20)); 119 | toneAdj.addChangeListener((ChangeEvent e)->{ 120 | music.setToneMultiplier((float)((double)toneAdj.getValue())); 121 | }); 122 | c.insets = new Insets(5,5,5,5); 123 | c.anchor = GridBagConstraints.NORTHEAST; 124 | c.fill = GridBagConstraints.VERTICAL; 125 | c.gridx = 2; 126 | c.gridy = 3; 127 | c.gridwidth = 1; 128 | c.gridheight = 1; 129 | c.weightx = 0; 130 | c.weighty = 0; 131 | add(toneAdj, c); 132 | 133 | description.setForeground(Color.DARK_GRAY); 134 | } 135 | 136 | @Override 137 | public void setFont(Font f) { 138 | super.setFont(f); 139 | if (description != null) { 140 | description.setFont(new Font(f.getFontName(), Font.PLAIN, (int)(f.getSize()*1.5d))); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/Complex.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | /****************************************************************************** 3 | * Compilation: javac Complex.java 4 | * Execution: java Complex 5 | * 6 | * Data type for complex numbers. 7 | * 8 | * The data type is "immutable" so once you create and initialize 9 | * a Complex object, you cannot change it. The "final" keyword 10 | * when declaring re and im enforces this rule, making it a 11 | * compile-time error to change the .re or .im instance variables after 12 | * they've been initialized. 13 | * 14 | * % java Complex 15 | * a = 5.0 + 6.0i 16 | * b = -3.0 + 4.0i 17 | * Re(a) = 5.0 18 | * Im(a) = 6.0 19 | * b + a = 2.0 + 10.0i 20 | * a - b = 8.0 + 2.0i 21 | * a * b = -39.0 + 2.0i 22 | * b * a = -39.0 + 2.0i 23 | * a / b = 0.36 - 1.52i 24 | * (a / b) * b = 5.0 + 6.0i 25 | * conj(a) = 5.0 - 6.0i 26 | * |a| = 7.810249675906654 27 | * tan(a) = -6.685231390246571E-6 + 1.0000103108981198i 28 | * 29 | ******************************************************************************/ 30 | 31 | import java.util.Objects; 32 | 33 | public class Complex { 34 | private final double re; // the real part 35 | private final double im; // the imaginary part 36 | 37 | // create a new object with the given real and imaginary parts 38 | public Complex(double real, double imag) { 39 | re = real; 40 | im = imag; 41 | } 42 | 43 | // return a string representation of the invoking Complex object 44 | public String toString() { 45 | if (im == 0) return re + ""; 46 | if (re == 0) return im + "i"; 47 | if (im < 0) return re + " - " + (-im) + "i"; 48 | return re + " + " + im + "i"; 49 | } 50 | 51 | // return abs/modulus/magnitude 52 | public double abs() { 53 | return Math.hypot(re, im); 54 | } 55 | 56 | // return angle/phase/argument, normalized to be between -pi and pi 57 | public double phase() { 58 | return Math.atan2(im, re); 59 | } 60 | 61 | // return a new Complex object whose value is (this + b) 62 | public Complex plus(Complex b) { 63 | Complex a = this; // invoking object 64 | double real = a.re + b.re; 65 | double imag = a.im + b.im; 66 | return new Complex(real, imag); 67 | } 68 | 69 | // return a new Complex object whose value is (this - b) 70 | public Complex minus(Complex b) { 71 | Complex a = this; 72 | double real = a.re - b.re; 73 | double imag = a.im - b.im; 74 | return new Complex(real, imag); 75 | } 76 | 77 | // return a new Complex object whose value is (this * b) 78 | public Complex times(Complex b) { 79 | Complex a = this; 80 | double real = a.re * b.re - a.im * b.im; 81 | double imag = a.re * b.im + a.im * b.re; 82 | return new Complex(real, imag); 83 | } 84 | 85 | // return a new object whose value is (this * alpha) 86 | public Complex scale(double alpha) { 87 | return new Complex(alpha * re, alpha * im); 88 | } 89 | 90 | // return a new Complex object whose value is the conjugate of this 91 | public Complex conjugate() { 92 | return new Complex(re, -im); 93 | } 94 | 95 | // return a new Complex object whose value is the reciprocal of this 96 | public Complex reciprocal() { 97 | double scale = re*re + im*im; 98 | return new Complex(re / scale, -im / scale); 99 | } 100 | 101 | // return the real or imaginary part 102 | public double re() { return re; } 103 | public double im() { return im; } 104 | 105 | // return a / b 106 | public Complex divides(Complex b) { 107 | Complex a = this; 108 | return a.times(b.reciprocal()); 109 | } 110 | 111 | // return a new Complex object whose value is the complex exponential of this 112 | public Complex exp() { 113 | return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im)); 114 | } 115 | 116 | // return a new Complex object whose value is the complex sine of this 117 | public Complex sin() { 118 | return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im)); 119 | } 120 | 121 | // return a new Complex object whose value is the complex cosine of this 122 | public Complex cos() { 123 | return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im)); 124 | } 125 | 126 | // return a new Complex object whose value is the complex tangent of this 127 | public Complex tan() { 128 | return sin().divides(cos()); 129 | } 130 | 131 | 132 | 133 | // a static version of plus 134 | public static Complex plus(Complex a, Complex b) { 135 | double real = a.re + b.re; 136 | double imag = a.im + b.im; 137 | Complex sum = new Complex(real, imag); 138 | return sum; 139 | } 140 | 141 | // See Section 3.3. 142 | public boolean equals(Object x) { 143 | if (x == null) return false; 144 | if (this.getClass() != x.getClass()) return false; 145 | Complex that = (Complex) x; 146 | return (this.re == that.re) && (this.im == that.im); 147 | } 148 | 149 | // See Section 3.3. 150 | public int hashCode() { 151 | return Objects.hash(re, im); 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/Midi23D.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d; 2 | 3 | import java.io.IOException; 4 | import java.util.Locale; 5 | 6 | import javax.sound.sampled.AudioFormat; 7 | import javax.sound.sampled.AudioSystem; 8 | import javax.sound.sampled.LineUnavailableException; 9 | import javax.sound.sampled.SourceDataLine; 10 | 11 | import org.warp.midito3d.music.DoneListener; 12 | import org.warp.midito3d.music.Music; 13 | import org.warp.midito3d.music.Note; 14 | import org.warp.midito3d.printers.GCodeOutput; 15 | import org.warp.midito3d.printers.Printer; 16 | 17 | public final class Midi23D implements DoneListener { 18 | 19 | public final Printer printer; 20 | public final Music music; 21 | public final GCodeOutput output; 22 | public final boolean motorTest; 23 | private int motorsCount; 24 | 25 | public Midi23D(Printer printer, Music music, GCodeOutput output, boolean motorTest) { 26 | this.printer = printer; 27 | this.music = music; 28 | this.output = output; 29 | this.motorTest = motorTest; 30 | } 31 | 32 | public void run() throws IOException { 33 | 34 | this.motorsCount = printer.getMotorsCount(); 35 | 36 | output.openAndLock(); 37 | 38 | music.setOutputChannelsCount(motorTest?1:motorsCount); 39 | music.reanalyze(this); 40 | } 41 | 42 | @Override 43 | public void done() { 44 | try { 45 | Note[] lastNotes = null; 46 | double lastTempo = 0; 47 | long lastTick = -1; 48 | double lastToneMultiplier = 0; 49 | double lastDivision = 0; 50 | printer.initialize(output); 51 | 52 | final double[][] debugFreqs = new double[printer.getMotorsCount()][(int) music.getLength()]; 53 | double songDuration = 0; 54 | 55 | // Wait 2 seconds at the start 56 | printer.wait(output, 2); 57 | 58 | do { 59 | long currentTick = music.getCurrentTick(); 60 | if (lastTick != -1) { 61 | double deltaTime = (((currentTick-lastTick) * lastDivision) * lastTempo) / music.getSpeedMultiplier(); 62 | 63 | double[] frequency = new double[motorsCount]; 64 | double[] speed = new double[motorsCount]; 65 | boolean didSomething = false; 66 | String frequenciesString = ""; 67 | 68 | for (int channel = 0; channel < motorsCount; channel++) { 69 | Note note = lastNotes[channel]; 70 | 71 | if (note != null) { 72 | frequency[channel] = note.getFrequency() * lastToneMultiplier; 73 | for (int ii = (int) lastTick + 1; ii <= currentTick; ii++) { 74 | debugFreqs[channel][ii] = frequency[channel]; 75 | } 76 | speed[channel] = frequency[channel] * 60d / (double)printer.getMotor(channel).getStepsPerMillimeter(); // mm/min 77 | 78 | if (didSomething == false) { 79 | didSomething = speed[channel] > 0d; 80 | } 81 | } 82 | 83 | frequenciesString += String.format(Locale.US, ", %.3fHz", frequency[channel]); 84 | } 85 | 86 | songDuration+=deltaTime; 87 | System.out.println(String.format("Chord: [%s] for %d deltas (%.2f seconds)", frequenciesString.substring(2), currentTick-lastTick, deltaTime)); 88 | 89 | 90 | if (didSomething) { 91 | printer.move(output, deltaTime, speed); 92 | } else { 93 | printer.wait(output, deltaTime); 94 | } 95 | } 96 | lastTick = currentTick; 97 | lastNotes = new Note[motorsCount]; 98 | for (int i = 0; i < motorsCount; i++) { 99 | lastNotes[i] = music.getCurrentNote(i); 100 | } 101 | lastTempo = music.getCurrentTempo(); 102 | lastToneMultiplier = music.getToneMultiplier(); 103 | lastDivision = music.getDivision(); 104 | music.findNext(); 105 | } while(music.hasNext()); 106 | 107 | // Wait 5 seconds at the end 108 | printer.wait(output, 5); 109 | 110 | System.out.println("Song duration: "+ songDuration + " seconds."); 111 | 112 | printer.stop(output); 113 | 114 | output.close(); 115 | 116 | System.out.println("Done."); 117 | 118 | // debugMusic(debugFreqs, songDuration, printer.getMotorsCount()); 119 | } catch (IOException e) { 120 | // TODO Auto-generated catch block 121 | e.printStackTrace(); 122 | } 123 | } 124 | 125 | @SuppressWarnings("unused") 126 | private static void debugMusic(final double[][] freqs, final double songDuration, final double actuatorsCount) { 127 | System.out.println("Debugging music. Duration=" + String.format("%.2f", songDuration) + " seconds. " + actuatorsCount + " channels."); 128 | for (int chan = 0; chan < 1; chan++) { 129 | final int chanF = chan; 130 | new Thread(() -> { 131 | try { 132 | createToneList(); 133 | for (int idx = 0; idx < freqs[chanF].length; idx++) { 134 | writeTone((int) (freqs[chanF][idx]), (int) (songDuration*1000d/freqs[0].length), 0.5d / actuatorsCount * (actuatorsCount - chanF)); 135 | //writeTone(265, 23, 0.5d); 136 | } 137 | startToneList(); 138 | System.out.println("track ended."); 139 | } catch (Exception e) { 140 | e.printStackTrace(); 141 | } 142 | }).start(); 143 | } 144 | } 145 | 146 | private static float SAMPLE_RATE = 8000f; 147 | @SuppressWarnings("unused") 148 | private static void writeTone(int hz, int msecs) throws LineUnavailableException { 149 | writeTone(hz, msecs, 0.5d); 150 | } 151 | private static SourceDataLine sdl; 152 | private static void createToneList() throws LineUnavailableException { 153 | AudioFormat af = new AudioFormat(SAMPLE_RATE,8,1,true,false); 154 | sdl = AudioSystem.getSourceDataLine(af); 155 | sdl.open(af); 156 | sdl.start(); 157 | } 158 | private static void startToneList() throws LineUnavailableException { 159 | sdl.drain(); 160 | sdl.stop(); 161 | sdl.close(); 162 | } 163 | 164 | private static void writeTone(int hz, int msecs, double vol) throws LineUnavailableException { 165 | byte[] buf = new byte[1]; 166 | for (int i=0; i < msecs*8; i++) { 167 | double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI; 168 | buf[0] = (byte)(Math.sin(angle) * 127.0 * vol); 169 | sdl.write(buf,0,1); 170 | } 171 | } 172 | 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/org/jfugue/TimeFactor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | import javax.sound.midi.MetaMessage; 30 | import javax.sound.midi.MidiEvent; 31 | import javax.sound.midi.MidiMessage; 32 | import javax.sound.midi.Sequence; 33 | 34 | public final class TimeFactor 35 | { 36 | public static double DEFAULT_BPM = 120.0d; 37 | public static int QUARTER_DURATIONS_IN_WHOLE = 4; 38 | 39 | public static final double getTimeFactor(Sequence sequence, double bpm) 40 | { 41 | double divisionType = sequence.getDivisionType(); 42 | double resolution = sequence.getResolution(); 43 | 44 | // If division type is PPQ, resolution is ticks per beat. 45 | // Since a beat is the length of time given to a one quarter note, this essentially 46 | // means that ticks per beat == pulses per quarter note (PPQ or PPQN) 47 | if (divisionType == Sequence.PPQ) 48 | { 49 | // System.out.println("DivisionType is PPQ"); 50 | // System.out.println("Resolution is "+resolution); 51 | } else { 52 | // System.out.println("DivisionType is SMPTE"); 53 | } 54 | // Useful resources: http://www.borg.com/~jglatt/tech/midifile/tempo.htm and http://www.borg.com/~jglatt/tech/midifile/ppqn.htm 55 | 56 | // If bit 15 of division is a zero, the bits 14 thru 0 represent the number of delta-time ticks which make up a 57 | // quarter-note. For instance, if division is 96, then a time interval of an eighth-note between two events 58 | // in the file would be 48. 59 | // 60 | // If bit 15 of division is a one, delta-times in a file correspond to subdivisions of a second, in a way consistent 61 | // with SMPTE and MIDI time code. Bits 14 thru 8 contain one of the four values -24, -25, -29, or -30, corresponding 62 | // to the four standard SMPTE and MIDI time code formats (-29 corresponds to 30 drop frame), and represents the number 63 | // of frames per second. These negative numbers are stored in two's complement form. The second byte (stored positive) 64 | // is the resolution within a frame: typical values may be 4 (MIDI time code resolution), 8, 10, 80 (bit resolution), 65 | // or 100. This system allows exact specification of time- code-based tracks, but also allows millisecond-based tracks 66 | // by specifying 25 frames/sec and a resolution of 40 units per frame. If the events in a file are stored with bit 67 | // resolution of thirty-frame time code, the division word would be E250 hex. 68 | 69 | if (bpm == 0.0) 70 | { 71 | bpm = DEFAULT_BPM; 72 | } 73 | 74 | return 60000.0 / (resolution * bpm); 75 | } 76 | 77 | public static final byte[] convertToThreeTempoBytes(int tempo) 78 | { 79 | double tempoInMsPerBeat = TimeFactor.convertBPMToMicrosecondsPerBeat(tempo); 80 | double d1 = Math.floor(tempoInMsPerBeat / 16384.0); 81 | double d2 = Math.floor((tempoInMsPerBeat % 16384.0) / 128.0); 82 | double d3 = Math.floor((tempoInMsPerBeat % 16384.0) % 128.0); 83 | return new byte[] { (byte)d1, (byte)d2, (byte)d3 }; 84 | } 85 | 86 | public static final int parseMicrosecondsPerBeat(MetaMessage message, long timestamp) 87 | { 88 | int tempo = message.getData()[0]*16384 + message.getData()[1]*128 + message.getData()[2]; 89 | int beatsPerMinute = (int)convertMicrosecondsPerBeatToBPM(tempo); 90 | return beatsPerMinute; 91 | } 92 | 93 | /** Converts microseconds per beat to BPM -- and vice versa */ 94 | public static final double convertMicrosecondsPerBeatToBPM(double value) { 95 | double microsecondsPerMinute = 60000000.0D; 96 | if (value == 0.0d) { 97 | return 0.0d; 98 | } 99 | return microsecondsPerMinute / value; 100 | } 101 | 102 | /** Converts microseconds per beat to BPM -- and vice versa */ 103 | public static final double convertBPMToMicrosecondsPerBeat(int bpm) { 104 | double microsecondsPerMinute = 60000000.0D; 105 | if (bpm == 0) { 106 | return 0; 107 | } 108 | return microsecondsPerMinute / bpm; 109 | } 110 | 111 | /** 112 | * Takes all of the MIDI events in the given Sequence, sorts them according to 113 | * when they are to be played, and sends the events to the MidiMessageRecipient 114 | * when the each event is ready to be played. 115 | * 116 | * @param sequence The Sequence with messages to sort and deliver 117 | * @param recipient the handler of the delivered message 118 | */ 119 | public static final void sortAndDeliverMidiMessages(Sequence sequence, MidiMessageRecipient recipient) 120 | { 121 | double timeFactor = 1.0; 122 | 123 | Map> timeMap = new HashMap>(); 124 | long longestTime = TimeEventManager.sortSequenceByTimestamp(sequence, timeMap); 125 | 126 | long lastTime = 0; 127 | for (long time=0; time < longestTime; time++) 128 | { 129 | List midiEventList = (List)timeMap.get(time); 130 | if (midiEventList != null) 131 | { 132 | for (MidiEvent event : midiEventList) 133 | { 134 | MidiMessage message = event.getMessage(); 135 | if ((message.getMessage().length >= 2) && (message.getMessage()[1] == 0x51) && (message instanceof MetaMessage)) 136 | { 137 | int bpm = parseMicrosecondsPerBeat((MetaMessage)message, time); 138 | timeFactor = TimeFactor.getTimeFactor(sequence, bpm); 139 | System.out.println("TimeFactor is "+timeFactor); 140 | } 141 | recipient.messageReady(message, time); 142 | } 143 | 144 | try 145 | { 146 | long sleepTime = (int)(((time - lastTime) * (TimeFactor.QUARTER_DURATIONS_IN_WHOLE+0.20))); 147 | Thread.sleep(sleepTime); // (int) (1 * timeFactor)); 148 | lastTime = time; 149 | } catch (Exception ex) 150 | { 151 | throw new RuntimeException("ERROR_SLEEP"); 152 | } 153 | } 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled 3 | org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore 4 | org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull 5 | org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= 6 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault 7 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= 8 | org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable 9 | org.eclipse.jdt.core.compiler.annotation.nullable.secondary= 10 | org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 11 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 12 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 13 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 14 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 15 | org.eclipse.jdt.core.compiler.compliance=1.8 16 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 17 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 18 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 19 | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning 20 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 21 | org.eclipse.jdt.core.compiler.problem.autoboxing=ignore 22 | org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning 23 | org.eclipse.jdt.core.compiler.problem.deadCode=warning 24 | org.eclipse.jdt.core.compiler.problem.deprecation=warning 25 | org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled 26 | org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled 27 | org.eclipse.jdt.core.compiler.problem.discouragedReference=warning 28 | org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore 29 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 30 | org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore 31 | org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore 32 | org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled 33 | org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore 34 | org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning 35 | org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning 36 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 37 | org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning 38 | org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled 39 | org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning 40 | org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning 41 | org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore 42 | org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore 43 | org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning 44 | org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore 45 | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore 46 | org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled 47 | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore 48 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore 49 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled 50 | org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning 51 | org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore 52 | org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning 53 | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning 54 | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore 55 | org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning 56 | org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning 57 | org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error 58 | org.eclipse.jdt.core.compiler.problem.nullReference=warning 59 | org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error 60 | org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning 61 | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning 62 | org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore 63 | org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning 64 | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore 65 | org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore 66 | org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore 67 | org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning 68 | org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning 69 | org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore 70 | org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore 71 | org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore 72 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore 73 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore 74 | org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled 75 | org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning 76 | org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled 77 | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled 78 | org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled 79 | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore 80 | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning 81 | org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled 82 | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning 83 | org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning 84 | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore 85 | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning 86 | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore 87 | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore 88 | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore 89 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore 90 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled 91 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled 92 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled 93 | org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore 94 | org.eclipse.jdt.core.compiler.problem.unusedImport=warning 95 | org.eclipse.jdt.core.compiler.problem.unusedLabel=warning 96 | org.eclipse.jdt.core.compiler.problem.unusedLocal=warning 97 | org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore 98 | org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore 99 | org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled 100 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled 101 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled 102 | org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning 103 | org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore 104 | org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning 105 | org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning 106 | org.eclipse.jdt.core.compiler.source=1.8 107 | -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/MainWindow.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui; 2 | 3 | import java.awt.Color; 4 | import java.awt.Container; 5 | import java.awt.Dimension; 6 | import java.awt.Font; 7 | import java.awt.GridBagConstraints; 8 | import java.awt.GridBagLayout; 9 | import java.awt.GridLayout; 10 | import java.awt.Image; 11 | import java.awt.Insets; 12 | import java.io.File; 13 | import java.io.IOException; 14 | import javax.imageio.ImageIO; 15 | import javax.sound.midi.InvalidMidiDataException; 16 | import javax.sound.sampled.UnsupportedAudioFileException; 17 | import javax.swing.JFrame; 18 | import javax.swing.JLabel; 19 | import javax.swing.JPanel; 20 | import javax.swing.WindowConstants; 21 | 22 | import org.warp.midito3d.FilenameUtils; 23 | import org.warp.midito3d.Midi23D; 24 | import org.warp.midito3d.gui.printers.PrinterModel; 25 | import org.warp.midito3d.gui.printers.PrinterModelArea; 26 | import org.warp.midito3d.music.Music; 27 | import org.warp.midito3d.music.midi.MidiParser; 28 | import org.warp.midito3d.music.mp3.Mp3Parser; 29 | import org.warp.midito3d.gui.ModernDialog.ModernExtensionFilter; 30 | import org.warp.midito3d.printers.GCodeOutput; 31 | import org.warp.midito3d.printers.Printer; 32 | 33 | import javazoom.jl.decoder.BitstreamException; 34 | import javazoom.jl.decoder.DecoderException; 35 | 36 | 37 | public class MainWindow extends JFrame { 38 | 39 | private static final long serialVersionUID = -3506064352026393354L; 40 | 41 | static MainWindow INSTANCE; 42 | public SongPanel songPanel; 43 | 44 | public PrinterModel printerModel; 45 | public PrinterModelArea printerModelArea; 46 | public Music music; 47 | public JButton exportBtn; 48 | 49 | public MainWindow() { 50 | INSTANCE = this; 51 | 52 | try { 53 | this.setIconImage(ImageIO.read(this.getClass().getClassLoader().getResource("3DPrinter128.png"))); 54 | } catch (IOException e1) { 55 | e1.printStackTrace(); 56 | } 57 | this.setTitle("Midi23D"); 58 | this.setLayout(new GridLayout(1, 1)); 59 | 60 | GridBagConstraints c = new GridBagConstraints(); 61 | 62 | JPanel mainPanel = new JPanel(new GridBagLayout()); 63 | 64 | JPanel leftPanel = new JPanel(new GridBagLayout()); 65 | JPanel rightPanel = new JPanel(new GridBagLayout()); 66 | 67 | JLabel leftPanelName = new JLabel("Input song"); 68 | songPanel = new OpenSongPanel(); 69 | JButton openMidiButton = new JButton("Open..."); 70 | exportBtn = new JButton("Export..."); 71 | 72 | JLabel rightPanelName = new JLabel("Printer settings"); 73 | PrinterPanel printerPanel = new PrinterPanel(); 74 | 75 | this.add(mainPanel); 76 | 77 | c.insets = new Insets(0,0,0,0); 78 | c.fill = GridBagConstraints.BOTH; 79 | c.weightx = 1.5; 80 | c.weighty = 1; 81 | c.gridx = 0; 82 | c.gridy = 0; 83 | mainPanel.add(leftPanel,c); 84 | c.insets = new Insets(0,0,0,0); 85 | c.fill = GridBagConstraints.BOTH; 86 | c.weightx = 2; 87 | c.weighty = 1; 88 | c.gridx = 1; 89 | c.gridy = 0; 90 | mainPanel.add(rightPanel,c); 91 | 92 | c.insets = new Insets(5,5,2,3); 93 | c.fill = GridBagConstraints.BOTH; 94 | c.weightx = 0; 95 | c.weighty = 0; 96 | c.gridx = 0; 97 | c.gridy = 0; 98 | c.gridwidth = 2; 99 | leftPanel.add(leftPanelName, c); 100 | c.insets = new Insets(0,5,0,3); 101 | c.weightx = 1; 102 | c.weighty = 1; 103 | c.gridy = 1; 104 | c.gridwidth = 2; 105 | leftPanel.add(songPanel, c); 106 | c.insets = new Insets(5,5,5,3); 107 | c.weightx = 0; 108 | c.weighty = 0; 109 | c.gridx = 0; 110 | c.gridy = 2; 111 | c.gridwidth = 1; 112 | leftPanel.add(openMidiButton, c); 113 | c.insets = new Insets(5,5,5,3); 114 | c.fill = GridBagConstraints.VERTICAL; 115 | c.anchor = GridBagConstraints.NORTHWEST; 116 | c.weightx = 1; 117 | c.weighty = 0; 118 | c.gridx = 1; 119 | c.gridy = 2; 120 | c.gridwidth = 1; 121 | leftPanel.add(exportBtn, c); 122 | 123 | c.insets = new Insets(5,2,2,5); 124 | c.fill = GridBagConstraints.BOTH; 125 | c.weightx = 0; 126 | c.weighty = 0; 127 | c.gridx = 0; 128 | c.gridy = 0; 129 | c.gridwidth = 2; 130 | rightPanel.add(rightPanelName, c); 131 | c.insets = new Insets(0,2,5,5); 132 | c.fill = GridBagConstraints.HORIZONTAL; 133 | c.anchor = GridBagConstraints.NORTH; 134 | c.weightx = 1; 135 | c.weighty = 1; 136 | c.gridy = 1; 137 | c.gridwidth = 2; 138 | rightPanel.add(printerPanel, c); 139 | 140 | songPanel.setMinimumSize(new Dimension(150, 130)); 141 | songPanel.setPreferredSize(new Dimension(350, 200)); 142 | songPanel.setMaximumSize(new Dimension(350, 200)); 143 | leftPanel.setBackground(Color.white); 144 | rightPanel.setBackground(Color.white); 145 | this.setBackground(Color.white); 146 | this.setMinimumSize(new Dimension(630, 370)); 147 | this.setPreferredSize(new Dimension(640, 400)); 148 | this.pack(); 149 | 150 | this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 151 | this.setFont(new Font("Segoe UI", Font.PLAIN, 12)); 152 | leftPanel.setFont(this.getFont()); 153 | leftPanelName.setFont(this.getFont()); 154 | songPanel.setFont(this.getFont()); 155 | openMidiButton.setFont(this.getFont()); 156 | openMidiButton.addActionListener((e)->{ 157 | ModernDialog.runLater(()->{ 158 | ModernDialog diag = new ModernDialog(); 159 | diag.setTitle("Open Midi File"); 160 | diag.setExtensions(new ModernExtensionFilter("Midi files", "*.midi", "*.mid"), /*new ModernExtensionFilter("Mp3 or WAV files", "*.mp3", "*.wav"),*/ new ModernExtensionFilter("All files", "*.*")); 161 | File f = diag.show(this); 162 | if (f != null && f.exists()) { 163 | importMidi(f); 164 | } 165 | }); 166 | }); 167 | exportBtn.setFont(this.getFont()); 168 | exportBtn.addActionListener((e)->{ 169 | exportMidiDialog(); 170 | }); 171 | exportBtn.setVisible(false); 172 | rightPanel.setFont(this.getFont()); 173 | rightPanelName.setFont(this.getFont()); 174 | printerPanel.setFont(this.getFont()); 175 | } 176 | 177 | private synchronized void importMidi(File f) { 178 | try { 179 | String ext = FilenameUtils.getExtension(f.toString()); 180 | Music mus = null; 181 | if (ext.length() > 0) { 182 | if (ext.equals("mid") || ext.equals("midi")) { 183 | System.out.println("Importing midi file..."); 184 | mus = MidiParser.loadFrom(f.toString(), true); 185 | System.out.println("Imported successfully."); 186 | } else /*if (ext.equals("mp3"))*/ { 187 | System.out.println("Importing mp3 file..."); 188 | mus = Mp3Parser.loadFrom(f.toString(), true); 189 | System.out.println("Imported successfully."); 190 | } 191 | } 192 | if (mus == null) { 193 | throw new java.lang.UnsupportedOperationException(); 194 | } 195 | this.music = mus; 196 | Container parent = songPanel.getParent(); 197 | parent.remove(songPanel); 198 | songPanel = new InputSongPanel(f, mus); 199 | songPanel.setMinimumSize(new Dimension(150, 130)); 200 | songPanel.setPreferredSize(new Dimension(350, 200)); 201 | songPanel.setMaximumSize(new Dimension(350, 200)); 202 | songPanel.setFont(this.getFont()); 203 | GridBagConstraints c = new GridBagConstraints(); 204 | c.fill = GridBagConstraints.BOTH; 205 | c.insets = new Insets(0,5,0,3); 206 | c.weightx = 1; 207 | c.weighty = 1; 208 | c.gridy = 1; 209 | c.gridwidth = 2; 210 | parent.add(songPanel,c); 211 | exportBtn.setVisible(true); 212 | this.validate(); 213 | this.repaint(); 214 | } catch (InvalidMidiDataException | IOException | UnsupportedAudioFileException | DecoderException | BitstreamException e) { 215 | e.printStackTrace(); 216 | } 217 | } 218 | 219 | private synchronized void exportMidiDialog() { 220 | ModernDialog.runLater(()->{ 221 | ModernDialog diag = new ModernDialog(); 222 | diag.setTitle("Save G-CODE File"); 223 | diag.setExtensions(new ModernExtensionFilter("G-CODE files", "*.gco", "*.gcode"), new ModernExtensionFilter("All files", "*.*")); 224 | File f = diag.showSaveDialog(this); 225 | if (f != null) { 226 | exportMidi(f); 227 | } 228 | }); 229 | } 230 | 231 | private synchronized void exportMidi(File output) { 232 | Printer p = printerModel.createPrinterObject(printerModelArea); 233 | Midi23D midi23D = new Midi23D(p, music, new GCodeOutput(output), false); 234 | try { 235 | midi23D.run(); 236 | } catch (IOException e) { 237 | e.printStackTrace(); 238 | } 239 | } 240 | 241 | public synchronized void execute() { 242 | this.setLocationRelativeTo(null); 243 | this.setVisible(true); 244 | } 245 | 246 | /* 247 | Map desktopHints = (Map) Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints"); 248 | 249 | Graphics2D g2d = (Graphics2D) g; 250 | if (desktopHints != null) { 251 | g2d.setRenderingHints(desktopHints); 252 | } 253 | 254 | * 255 | * 256 | * 257 | 258 | RenderingHints rh = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 259 | rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 260 | rh.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 261 | rh.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); 262 | rh.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); 263 | rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 264 | 265 | */ 266 | 267 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/gui/PrinterPanel.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.gui; 2 | 3 | import static org.warp.midito3d.gui.printers.StandardPrinters.ANET_A8_DEFAULT_AXIS_STEPS_PER_MM; 4 | import static org.warp.midito3d.gui.printers.StandardPrinters.LABISTS_ET4_DEFAULT_AXIS_STEPS_PER_MM; 5 | 6 | import java.awt.Color; 7 | import java.awt.Component; 8 | import java.awt.Dimension; 9 | import java.awt.Font; 10 | import java.awt.GridBagConstraints; 11 | import java.awt.GridBagLayout; 12 | import java.awt.Insets; 13 | import java.awt.event.ItemEvent; 14 | import java.awt.event.ItemListener; 15 | 16 | import javax.swing.BorderFactory; 17 | import javax.swing.event.ChangeEvent; 18 | import javax.swing.event.ChangeListener; 19 | import javax.swing.BoxLayout; 20 | import javax.swing.JComboBox; 21 | import javax.swing.JLabel; 22 | import javax.swing.JPanel; 23 | import javax.swing.JSpinner; 24 | import javax.swing.SpinnerNumberModel; 25 | import javax.swing.SwingConstants; 26 | 27 | import org.warp.midito3d.gui.printers.Model2Axes; 28 | import org.warp.midito3d.gui.printers.Model3Axes; 29 | import org.warp.midito3d.gui.printers.Model4Axes; 30 | import org.warp.midito3d.gui.printers.ModelZAxis; 31 | import org.warp.midito3d.gui.printers.PrinterModel; 32 | import org.warp.midito3d.gui.printers.PrinterModelArea; 33 | 34 | public class PrinterPanel extends JPanel { 35 | 36 | private static final long serialVersionUID = 3730582196639810443L; 37 | private JImage modelImg; 38 | private JPanel motorList; 39 | private Component marginsImg; 40 | 41 | 42 | public PrinterPanel() { 43 | GridBagConstraints c = new GridBagConstraints(); 44 | 45 | super.setLayout(new GridBagLayout()); 46 | 47 | this.setBackground(Color.white); 48 | this.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 49 | 50 | modelImg = JImage.loadFromResources("3DPrinter128.png"); 51 | modelImg.setPreferredSize(new Dimension(128, 128)); 52 | modelImg.setMinimumSize(new Dimension(64, 128)); 53 | modelImg.setMaximumSize(new Dimension(128, 128)); 54 | c.insets = new Insets(5,5,0,3); 55 | c.fill = GridBagConstraints.BOTH; 56 | c.anchor = GridBagConstraints.NORTHWEST; 57 | c.weightx = 1; 58 | c.weighty = 1; 59 | c.gridx = 0; 60 | c.gridy = 0; 61 | c.gridwidth = 5; 62 | c.gridheight = 2; 63 | this.add(modelImg, c); 64 | JLabel modelText = new JLabel("Model"); 65 | modelText.setVerticalAlignment(SwingConstants.BOTTOM); 66 | c.insets = new Insets(5,5,0,3); 67 | c.fill = GridBagConstraints.VERTICAL; 68 | c.anchor = GridBagConstraints.SOUTHWEST; 69 | c.weightx = 0; 70 | c.weighty = 1; 71 | c.gridx = 5; 72 | c.gridy = 0; 73 | c.gridwidth = 5; 74 | c.gridheight = 1; 75 | this.add(modelText, c); 76 | JComboBox modeList = new JComboBox<>(new PrinterModel[]{ 77 | new ModelZAxis("A8", ANET_A8_DEFAULT_AXIS_STEPS_PER_MM), 78 | new ModelZAxis("ET4", LABISTS_ET4_DEFAULT_AXIS_STEPS_PER_MM), 79 | new Model2Axes("A8", ANET_A8_DEFAULT_AXIS_STEPS_PER_MM), 80 | new Model2Axes("ET4", LABISTS_ET4_DEFAULT_AXIS_STEPS_PER_MM), 81 | new Model3Axes("A8", ANET_A8_DEFAULT_AXIS_STEPS_PER_MM), 82 | new Model3Axes("ET4", LABISTS_ET4_DEFAULT_AXIS_STEPS_PER_MM), 83 | new Model4Axes("A8", ANET_A8_DEFAULT_AXIS_STEPS_PER_MM), 84 | new Model4Axes("ET4", LABISTS_ET4_DEFAULT_AXIS_STEPS_PER_MM)}); 85 | modeList.setMinimumSize(new Dimension(130, 20)); 86 | modeList.setPreferredSize(new Dimension(130, 20)); 87 | c.insets = new Insets(0,5,2,3); 88 | c.fill = GridBagConstraints.NONE; 89 | c.anchor = GridBagConstraints.NORTHWEST; 90 | c.weightx = 0; 91 | c.weighty = 1; 92 | c.gridx = 5; 93 | c.gridy = 1; 94 | c.gridwidth = 4; 95 | c.gridheight = 1; 96 | this.add(modeList, c); 97 | JLabel motorListText = new JLabel("Motors"); 98 | motorListText.setVerticalAlignment(SwingConstants.BOTTOM); 99 | motorListText.setHorizontalAlignment(SwingConstants.CENTER); 100 | Font f = motorListText.getFont(); 101 | motorListText.setFont(new Font(f.getFontName(), Font.BOLD, f.getSize())); 102 | c.insets = new Insets(5,5,0,3); 103 | c.fill = GridBagConstraints.BOTH; 104 | c.anchor = GridBagConstraints.SOUTHWEST; 105 | c.weightx = 0; 106 | c.weighty = 0; 107 | c.gridx = 0; 108 | c.gridy = 2; 109 | c.gridwidth = 2; 110 | c.gridheight = 1; 111 | this.add(motorListText, c); 112 | motorList = new JPanel(); 113 | motorList.setLayout(new BoxLayout(motorList, BoxLayout.Y_AXIS)); 114 | motorList.setPreferredSize(new Dimension(1000, 128)); 115 | motorList.setMinimumSize(new Dimension(64, 64)); 116 | motorList.setBackground(new Color(0,0,0,0)); 117 | c.insets = new Insets(0,0,0,0); 118 | c.fill = GridBagConstraints.BOTH; 119 | c.anchor = GridBagConstraints.NORTHWEST; 120 | c.weightx = 1; 121 | c.weighty = 0; 122 | c.gridx = 0; 123 | c.gridy = 3; 124 | c.gridwidth = 10; 125 | c.gridheight = 1; 126 | this.add(motorList, c); 127 | JPanel bottomPan = new JPanel(); 128 | bottomPan.setMinimumSize(new Dimension(300,100)); 129 | bottomPan.setMaximumSize(new Dimension(300,100)); 130 | bottomPan.setLayout(null); 131 | bottomPan.setBackground(this.getBackground()); 132 | c.insets = new Insets(5,5,5,5); 133 | c.fill = GridBagConstraints.NONE; 134 | c.anchor = GridBagConstraints.CENTER; 135 | c.weightx = 0; 136 | c.weighty = 0; 137 | c.gridx = 0; 138 | c.gridy = 4; 139 | c.gridwidth = 10; 140 | c.gridheight = 1; 141 | this.add(bottomPan, c); 142 | MainWindow.INSTANCE.printerModelArea = new PrinterModelArea(new int[]{100,100,10,100}, new int[]{15,15,2,2}); 143 | final JSpinner zSize = new JSpinner(new SpinnerNumberModel(MainWindow.INSTANCE.printerModelArea.size[2], 1, 1000, 1)); 144 | zSize.setSize(55,20); 145 | zSize.setLocation(00,50); 146 | zSize.addChangeListener((ChangeEvent e)->{ 147 | MainWindow.INSTANCE.printerModelArea.size[2] = (int) zSize.getValue(); 148 | }); 149 | bottomPan.add(zSize, c); 150 | final JSpinner xSize = new JSpinner(new SpinnerNumberModel(MainWindow.INSTANCE.printerModelArea.size[0], 1, 1000, 1)); 151 | xSize.setSize(55,20); 152 | xSize.setLocation(10,80); 153 | xSize.addChangeListener((ChangeEvent e)->{ 154 | MainWindow.INSTANCE.printerModelArea.size[0] = (int) xSize.getValue(); 155 | }); 156 | bottomPan.add(xSize, c); 157 | final JSpinner ySize = new JSpinner(new SpinnerNumberModel(MainWindow.INSTANCE.printerModelArea.size[1], 1, 1000, 1)); 158 | ySize.setSize(55,20); 159 | ySize.setLocation(95,80); 160 | ySize.addChangeListener((ChangeEvent e)->{ 161 | MainWindow.INSTANCE.printerModelArea.size[1] = (int) ySize.getValue(); 162 | }); 163 | bottomPan.add(ySize, c); 164 | final JSpinner yMargin = new JSpinner(new SpinnerNumberModel(MainWindow.INSTANCE.printerModelArea.margins[1], 1, 1000, 1)); 165 | yMargin.setSize(55,20); 166 | yMargin.setLocation(145,15); 167 | yMargin.addChangeListener((ChangeEvent e)->{ 168 | MainWindow.INSTANCE.printerModelArea.margins[1] = (int) yMargin.getValue(); 169 | }); 170 | bottomPan.add(yMargin, c); 171 | final JSpinner zMargin = new JSpinner(new SpinnerNumberModel(MainWindow.INSTANCE.printerModelArea.margins[2], 1, 1000, 1)); 172 | zMargin.setSize(55,20); 173 | zMargin.setLocation(120,55); 174 | zMargin.addChangeListener((ChangeEvent e)->{ 175 | MainWindow.INSTANCE.printerModelArea.margins[2] = (int) zMargin.getValue(); 176 | }); 177 | bottomPan.add(zMargin, c); 178 | final JSpinner xMargin = new JSpinner(new SpinnerNumberModel(MainWindow.INSTANCE.printerModelArea.margins[0], 1, 1000, 1)); 179 | xMargin.setSize(55,20); 180 | xMargin.setLocation(235,15); 181 | xMargin.addChangeListener((ChangeEvent e)->{ 182 | MainWindow.INSTANCE.printerModelArea.margins[0] = (int) xMargin.getValue(); 183 | }); 184 | bottomPan.add(xMargin, c); 185 | JImage areaImg = JImage.loadFromResources("PrinterArea100.png"); 186 | areaImg.setSize(100, 100); 187 | areaImg.setLocation(25, 0); 188 | bottomPan.add(areaImg, c); 189 | marginsImg = JImage.loadFromResources("PrinterMargins100.png"); 190 | marginsImg.setSize(100, 100); 191 | marginsImg.setLocation(160, 0); 192 | bottomPan.add(marginsImg, c); 193 | 194 | modeList.addItemListener(new ItemListener(){ 195 | @Override 196 | public void itemStateChanged(ItemEvent event) { 197 | if (event.getStateChange() == ItemEvent.SELECTED) { 198 | PrinterModel item = (PrinterModel) event.getItem(); 199 | setPrinterModel(item); 200 | } 201 | } 202 | }); 203 | modeList.setSelectedIndex(2); 204 | } 205 | 206 | public void setPrinterModel(PrinterModel model) { 207 | MainWindow.INSTANCE.printerModel = model; 208 | motorList.removeAll(); 209 | motorList.setMinimumSize(new Dimension(64, model.getMotorsCount()*25)); 210 | motorList.setMaximumSize(new Dimension(1000, model.getMotorsCount()*25)); 211 | GridBagConstraints c = new GridBagConstraints(); 212 | for (int i = 0; i < model.getMotorsCount(); i++) { 213 | final int fi = i; 214 | JPanel p = new JPanel(); 215 | p.setLayout(new GridBagLayout()); 216 | p.setBackground(new Color(0,0,0,0)); 217 | 218 | JLabel motLabel = new JLabel(model.getMotorName(i)); 219 | motLabel.setVerticalAlignment(SwingConstants.CENTER); 220 | motLabel.setMinimumSize(new Dimension(50,20)); 221 | motLabel.setPreferredSize(new Dimension(50,20)); 222 | motLabel.setMaximumSize(new Dimension(9999,20)); 223 | c.insets = new Insets(0,5,0,3); 224 | c.fill = GridBagConstraints.BOTH; 225 | c.anchor = GridBagConstraints.WEST; 226 | c.weightx = 0; 227 | c.weighty = 0; 228 | c.gridx = 0; 229 | c.gridy = 0; 230 | c.gridwidth = 1; 231 | c.gridheight = 1; 232 | p.add(motLabel, c); 233 | final JSpinner motVal = new JSpinner(new SpinnerNumberModel(model.getMotor(i).ppi, 1, 2000, 10)); 234 | motVal.setMinimumSize(new Dimension(80,20)); 235 | motVal.setPreferredSize(new Dimension(80,20)); 236 | motVal.setMaximumSize(new Dimension(9999,20)); 237 | motVal.addChangeListener(new ChangeListener() { 238 | 239 | @Override 240 | public void stateChanged(ChangeEvent e) { 241 | model.getMotor(fi).ppi = (int) motVal.getValue(); 242 | System.out.println(model.getMotor(fi).ppi); 243 | } 244 | }); 245 | c.insets = new Insets(0,5,0,3); 246 | c.fill = GridBagConstraints.BOTH; 247 | c.anchor = GridBagConstraints.WEST; 248 | c.weightx = 0; 249 | c.weighty = 0; 250 | c.gridx = 1; 251 | c.gridy = 0; 252 | c.gridwidth = 1; 253 | c.gridheight = 1; 254 | p.add(motVal, c); 255 | motorList.add(p); 256 | } 257 | motorList.revalidate(); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/org/jfugue/Controller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | /** 26 | * Contains information for MIDI Controller Events. 27 | * 28 | *@author David Koelle 29 | *@version 2.0 30 | */ 31 | public final class Controller implements JFugueElement 32 | { 33 | byte index; 34 | byte value; 35 | 36 | /** Creates a new Controller object */ 37 | public Controller() 38 | { 39 | this.index = 0; 40 | this.value = 0; 41 | } 42 | 43 | /** 44 | * Creates a new Controller object, with the specified controller index and value. 45 | * 46 | * @param index the index of the controller to set 47 | * @param value the byte value used to set the controller 48 | */ 49 | public Controller(byte index, byte value) 50 | { 51 | this.index = index; 52 | this.value = value; 53 | } 54 | 55 | /** 56 | * TODO: This method, which is currently not supported, 57 | * is intended to take an integer, which contains the MSB and LSB bytes of a controller event, 58 | * and parse the integer to the MSB and LSB. Currently, this feature is handled by MusicStringParser, and 59 | * has not been rolled into the Controller class (where it should really belong). One difficulty to consider 60 | * is that an integer representing an MSB and an LSB actually means that there are two Controller instances, 61 | * not just one. Of course, the Controller class itself represents only one controller event. 62 | * 63 | * @param index 64 | * @param value 65 | */ 66 | // public Controller(int index, byte value) 67 | // { 68 | // throw new UnsupportedOperationException("Controller(int index, byte value) is not supported. If you're using a byte for the controller index, cast your index appropriately to use Controller(byte index, byte value)."); 69 | // } 70 | 71 | /** 72 | * Sets the index of the controller event for this object. 73 | * @param index the index of the controller 74 | */ 75 | public void setIndex(byte index) 76 | { 77 | this.index = index; 78 | } 79 | 80 | /** 81 | * Returns the index of the controller event for this object. 82 | * @return the index of the controller 83 | */ 84 | public byte getIndex() 85 | { 86 | return this.index; 87 | } 88 | 89 | /** 90 | * Sets the value of the controller event for this object. 91 | * @param value the byte value used to set the controller 92 | */ 93 | public void setValue(byte value) 94 | { 95 | this.value = value; 96 | } 97 | 98 | /** 99 | * Returns the value of the controller event for this object. 100 | * @return the value of the controller 101 | */ 102 | public byte getValue() 103 | { 104 | return this.value; 105 | } 106 | 107 | /** 108 | * Returns the Music String representing this element and all of its settings. 109 | * For a Controller object, the Music String is Xindex=value 110 | * @return the Music String for this element 111 | */ 112 | public String getMusicString() 113 | { 114 | StringBuffer buffy = new StringBuffer(); 115 | buffy.append("X"); 116 | buffy.append(getIndex()); 117 | buffy.append("="); 118 | buffy.append(getValue()); 119 | return buffy.toString(); 120 | } 121 | 122 | /** 123 | * Returns verification string in this format: 124 | * Controller: index={#}, value={#} 125 | * @version 4.0 126 | */ 127 | public String getVerifyString() 128 | { 129 | StringBuffer buffy = new StringBuffer(); 130 | buffy.append("Controller: index="); 131 | buffy.append(getIndex()); 132 | buffy.append(", value="); 133 | buffy.append(getValue()); 134 | return buffy.toString(); 135 | } 136 | 137 | public static final byte BANK_SELECT_COARSE = 0; 138 | public static final byte MOD_WHEEL_COARSE = 1; 139 | public static final byte BREATH_COARSE = 2; 140 | public static final byte FOOT_PEDAL_COARSE = 4; 141 | public static final byte PORTAMENTO_TIME_COARSE = 5; 142 | public static final byte DATA_ENTRY_COARSE = 6; 143 | public static final byte VOLUME_COARSE = 7; 144 | public static final byte BALANCE_COARSE = 8; 145 | public static final byte PAN_POSITION_COARSE = 10; 146 | public static final byte EXPRESSION_COARSE = 11; 147 | public static final byte EFFECT_CONTROL_1_COARSE = 12; 148 | public static final byte EFFECT_CONTROL_2_COARSE = 13; 149 | 150 | public static final byte SLIDER_1 = 16; 151 | public static final byte SLIDER_2 = 17; 152 | public static final byte SLIDER_3 = 18; 153 | public static final byte SLIDER_4 = 19; 154 | 155 | public static final byte BANK_SELECT_FINE = 32; 156 | public static final byte MOD_WHEEL_FINE = 33; 157 | public static final byte BREATH_FINE = 34; 158 | public static final byte FOOT_PEDAL_FINE = 36; 159 | public static final byte PORTAMENTO_TIME_FINE = 37; 160 | public static final byte DATA_ENTRY_FINE = 38; 161 | public static final byte VOLUME_FINE = 39; 162 | public static final byte BALANCE_FINE = 40; 163 | public static final byte PAN_POSITION_FINE = 42; 164 | public static final byte EXPRESSION_FINE = 43; 165 | public static final byte EFFECT_CONTROL_1_FINE = 44; 166 | public static final byte EFFECT_CONTROL_2_FINE = 45; 167 | 168 | public static final byte HOLD_PEDAL = 64; 169 | public static final byte HOLD = 64; 170 | public static final byte PORTAMENTO = 65; 171 | public static final byte SUSTENUTO_PEDAL = 66; 172 | public static final byte SUSTENUTO = 66; 173 | public static final byte SOFT_PEDAL = 67; 174 | public static final byte SOFT = 67; 175 | public static final byte LEGATO_PEDAL = 68; 176 | public static final byte LEGATO = 68; 177 | public static final byte HOLD_2_PEDAL = 69; 178 | public static final byte HOLD_2 = 69; 179 | 180 | public static final byte SOUND_VARIATION = 70; 181 | public static final byte VARIATION = 70; 182 | public static final byte SOUND_TIMBRE = 71; 183 | public static final byte TIMBRE = 71; 184 | public static final byte SOUND_RELEASE_TIME = 72; 185 | public static final byte RELEASE_TIME = 72; 186 | public static final byte SOUND_ATTACK_TIME = 73; 187 | public static final byte ATTACK_TIME = 73; 188 | public static final byte SOUND_BRIGHTNESS = 74; 189 | public static final byte BRIGHTNESS = 74; 190 | public static final byte SOUND_CONTROL_6 = 75; 191 | public static final byte CONTROL_6 = 75; 192 | public static final byte SOUND_CONTROL_7 = 76; 193 | public static final byte CONTROL_7 = 76; 194 | public static final byte SOUND_CONTROL_8 = 77; 195 | public static final byte CONTROL_8 = 77; 196 | public static final byte SOUND_CONTROL_9 = 78; 197 | public static final byte CONTROL_9 = 78; 198 | public static final byte SOUND_CONTROL_10 = 79; 199 | public static final byte CONTROL_10 = 79; 200 | 201 | public static final byte GENERAL_PURPOSE_BUTTON_1 = 80; 202 | public static final byte GENERAL_BUTTON_1 = 80; 203 | public static final byte BUTTON_1 = 80; 204 | public static final byte GENERAL_PURPOSE_BUTTON_2 = 81; 205 | public static final byte GENERAL_BUTTON_2 = 81; 206 | public static final byte BUTTON_2 = 81; 207 | public static final byte GENERAL_PURPOSE_BUTTON_3 = 82; 208 | public static final byte GENERAL_BUTTON_3 = 82; 209 | public static final byte BUTTON_3 = 82; 210 | public static final byte GENERAL_PURPOSE_BUTTON_4 = 83; 211 | public static final byte GENERAL_BUTTON_4 = 83; 212 | public static final byte BUTTON_4 = 83; 213 | 214 | public static final byte EFFECTS_LEVEL = 91; 215 | public static final byte EFFECTS = 91; 216 | public static final byte TREMULO_LEVEL = 92; 217 | public static final byte TREMULO = 92; 218 | public static final byte CHORUS_LEVEL = 93; 219 | public static final byte CHORUS = 93; 220 | public static final byte CELESTE_LEVEL = 94; 221 | public static final byte CELESTE = 94; 222 | public static final byte PHASER_LEVEL = 95; 223 | public static final byte PHASER = 95; 224 | 225 | public static final byte DATA_BUTTON_INCREMENT = 96; 226 | public static final byte DATA_BUTTON_INC = 96; 227 | public static final byte BUTTON_INC = 96; 228 | public static final byte DATA_BUTTON_DECREMENT = 97; 229 | public static final byte DATA_BUTTON_DEC = 97; 230 | public static final byte BUTTON_DEC = 97; 231 | 232 | public static final byte NON_REGISTERED_COARSE = 98; 233 | public static final byte NON_REGISTERED_FINE = 99; 234 | public static final byte REGISTERED_COARSE = 100; 235 | public static final byte REGISTERED_FINE = 101; 236 | 237 | public static final byte ALL_SOUND_OFF = 120; 238 | public static final byte ALL_CONTROLLERS_OFF = 121; 239 | public static final byte LOCAL_KEYBOARD = 122; 240 | public static final byte ALL_NOTES_OFF = 123; 241 | public static final byte OMNI_MODE_OFF = 124; 242 | public static final byte OMNI_OFF = 124; 243 | public static final byte OMNI_MODE_ON = 125; 244 | public static final byte OMNI_ON = 125; 245 | public static final byte MONO_OPERATION = 126; 246 | public static final byte MONO = 126; 247 | public static final byte POLY_OPERATION = 127; 248 | public static final byte POLY = 127; 249 | 250 | // 251 | // Combined Controller names 252 | // (index = coarse_controller_index * 128 + fine_controller_index) 253 | // 254 | public static final int BANK_SELECT = 16383; 255 | public static final int MOD_WHEEL = 161; 256 | public static final int BREATH = 290; 257 | public static final int FOOT_PEDAL = 548; 258 | public static final int PORTAMENTO_TIME = 677; 259 | public static final int DATA_ENTRY = 806; 260 | public static final int VOLUME = 935; 261 | public static final int BALANCE = 1064; 262 | public static final int PAN_POSITION = 1322; 263 | public static final int EXPRESSION = 1451; 264 | public static final int EFFECT_CONTROL_1 = 1580; 265 | public static final int EFFECT_CONTROL_2 = 1709; 266 | public static final int NON_REGISTERED = 12770; 267 | public static final int REGISTERED = 13028; 268 | 269 | // 270 | // Values for controllers 271 | // 272 | public static final byte ON = 127; 273 | public static final byte OFF = 0; 274 | public static final byte DEFAULT = 64; 275 | 276 | } -------------------------------------------------------------------------------- /src/org/jfugue/MidiParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | import javax.sound.midi.MetaMessage; 26 | import javax.sound.midi.MidiEvent; 27 | import javax.sound.midi.MidiMessage; 28 | import javax.sound.midi.Sequence; 29 | import javax.sound.midi.ShortMessage; 30 | import javax.sound.midi.SysexMessage; 31 | import javax.sound.midi.Track; 32 | 33 | /** 34 | * Parses MIDI data, whether from a file, a connected device, or some other stream. 35 | * 36 | * @version 4.0.3 - A Note event with 0 duration is now sent when a note is first encountered 37 | */ 38 | public final class MidiParser extends Parser 39 | { 40 | long[][] tempNoteRegistry = new long[16][255]; 41 | byte[][] tempNoteAttackRegistry = new byte[16][255]; 42 | int tempo; 43 | private static final int DEFAULT_TEMPO = 120; 44 | 45 | public MidiParser() 46 | { 47 | this.tempo = DEFAULT_TEMPO; 48 | 49 | // Create a two dimensional array of bytes [ track, note ] - when a NoteOn event is found, 50 | // populate the proper spot in the array with the note's start time. When a NoteOff event 51 | // is found, new Time and Note objects are constructed and added to the composition 52 | for (int m=0; m < 16; m++) { 53 | for (int n=0; n < 255; n++) { 54 | tempNoteRegistry[m][n] = 0L; 55 | tempNoteAttackRegistry[m][n] = (byte)0; 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * Parses a Sequence and fires events to subscribed ParserListener 62 | * interfaces. As the Sequence is parsed, events are sent 63 | * to ParserListener interfaces, which are responsible for doing 64 | * something interesting with the music data, such as adding notes to a pattern. 65 | * 66 | * @param sequence the Sequence to parse 67 | * @throws Exception if there is an error parsing the pattern 68 | */ 69 | public void parse(Sequence sequence) 70 | { 71 | this.tempo = DEFAULT_TEMPO; 72 | 73 | // Get the MIDI tracks from the sequence. Expect a maximum of 16 tracks. 74 | Track[] tracks = sequence.getTracks(); 75 | 76 | // Compute the size of this adventure for the ParserProgressListener 77 | long totalCount = 0; 78 | long counter = 0; 79 | for (byte i=0; i < tracks.length; i++) 80 | { 81 | totalCount += tracks[i].size(); 82 | } 83 | 84 | 85 | // And now to parse the MIDI! 86 | for (int t = 0; t < tracks.length; t++) 87 | { 88 | int trackSize = tracks[t].size(); 89 | if (trackSize > 0) 90 | { 91 | fireVoiceEvent(new Voice((byte)t)); 92 | 93 | for (int ev = 0; ev < trackSize; ev++) 94 | { 95 | counter++; 96 | fireProgressReported("Parsing MIDI...", counter, totalCount); 97 | 98 | MidiEvent event = tracks[t].get(ev); 99 | MidiMessage message = event.getMessage(); 100 | 101 | trace("Message received: ",message); 102 | parse(message, event.getTick()); 103 | } 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * Delegator method that calls specific parsers depending on the 110 | * type of MidiMessage passed in. 111 | * @param message the message to parse 112 | * @param timestamp the time at which the message was encountered in this track 113 | */ 114 | public void parse(MidiMessage message, long timestamp) 115 | { 116 | if (message instanceof ShortMessage) 117 | { 118 | parseShortMessage((ShortMessage)message, timestamp); 119 | } 120 | else if (message instanceof SysexMessage) 121 | { 122 | parseSysexMessage((SysexMessage)message, timestamp); 123 | } 124 | else if (message instanceof MetaMessage) 125 | { 126 | parseMetaMessage((MetaMessage)message, timestamp); 127 | } 128 | } 129 | 130 | /** 131 | * Parses instances of ShortMessage. 132 | * @param message The message to parse 133 | * @param timestamp the time at which the message was encountered in this track 134 | */ 135 | private void parseShortMessage(ShortMessage message, long timestamp) 136 | { 137 | int track = message.getChannel(); 138 | 139 | switch (message.getCommand()) 140 | { 141 | case ShortMessage.PROGRAM_CHANGE : // 0xC0, 192 142 | trace("Program change to ",message.getData1()); 143 | Instrument instrument = new Instrument((byte)message.getData1()); 144 | fireTimeEvent(new Time(timestamp)); 145 | fireVoiceEvent(new Voice((byte)track)); 146 | fireInstrumentEvent(instrument); 147 | break; 148 | 149 | case ShortMessage.CONTROL_CHANGE : // 0xB0, 176 150 | trace("Controller change to ",message.getData1(),", value = ",message.getData2()); 151 | Controller controller = new Controller((byte)message.getData1(), (byte)message.getData2()); 152 | fireTimeEvent(new Time(timestamp)); 153 | fireVoiceEvent(new Voice((byte)track)); 154 | fireControllerEvent(controller); 155 | break; 156 | case ShortMessage.NOTE_ON : // 0x90, 144 157 | if (message.getData2() == 0) { 158 | // A velocity of zero in a note-on event is a note-off event 159 | noteOffEvent(timestamp, track, message.getData1(), message.getData2()); 160 | } else { 161 | noteOnEvent(timestamp, track, message.getData1(), message.getData2()); 162 | } 163 | break; 164 | case ShortMessage.NOTE_OFF : // 0x80, 128 165 | noteOffEvent(timestamp, track, message.getData1(), message.getData2()); 166 | break; 167 | case ShortMessage.CHANNEL_PRESSURE : // 0xD0, 208 168 | trace("Channel pressure, pressure = ",message.getData1()); 169 | ChannelPressure pressure = new ChannelPressure((byte)message.getData1()); 170 | fireTimeEvent(new Time(timestamp)); 171 | fireVoiceEvent(new Voice((byte)track)); 172 | fireChannelPressureEvent(pressure); 173 | break; 174 | case ShortMessage.POLY_PRESSURE : // 0xA0, 128 175 | trace("Poly pressure on key ",message.getData1(),", pressure = ",message.getData2()); 176 | PolyphonicPressure poly = new PolyphonicPressure((byte)message.getData1(), (byte)message.getData2()); 177 | fireTimeEvent(new Time(timestamp)); 178 | fireVoiceEvent(new Voice((byte)track)); 179 | firePolyphonicPressureEvent(poly); 180 | break; 181 | case ShortMessage.PITCH_BEND : // 0xE0, 224 182 | trace("Pitch Bend, data1= ",message.getData1(),", data2= ",message.getData2()); 183 | PitchBend bend = new PitchBend((byte)message.getData1(), (byte)message.getData2()); 184 | fireTimeEvent(new Time(timestamp)); 185 | fireVoiceEvent(new Voice((byte)track)); 186 | firePitchBendEvent(bend); 187 | break; 188 | default : 189 | trace("Unparsed message: ",message.getCommand()); 190 | break; 191 | } 192 | } 193 | 194 | private void noteOnEvent(long timestamp, int track, int data1, int data2) 195 | { 196 | trace("Note on ",data1," - attack is ",data2); 197 | tempNoteRegistry[track][data1] = timestamp; 198 | tempNoteAttackRegistry[track][data1] = (byte)data2; 199 | 200 | // Added 9/27/2008 - fire a Note with duration 0 to signify a that a Note was pressed 201 | Note note = new Note((byte)data1, 0); 202 | note.setDecimalDuration(0); 203 | note.setAttackVelocity((byte)data2); 204 | fireNoteEvent(note); 205 | } 206 | 207 | private void noteOffEvent(long timestamp, int track, int data1, int data2) 208 | { 209 | long time = tempNoteRegistry[track][data1]; 210 | trace("Note off ",data1," - decay is ",data2,". Duration is ",(timestamp - time)); 211 | 212 | fireTimeEvent(new Time(time)); 213 | fireVoiceEvent(new Voice((byte)track)); 214 | Note note = new Note((byte)data1, (long)(timestamp - time)); 215 | note.setDecimalDuration((double)((timestamp - time) / (this.tempo * 4.0D))); 216 | note.setAttackVelocity(tempNoteAttackRegistry[track][data1]); 217 | note.setDecayVelocity((byte)data2); 218 | fireNoteEvent(note); 219 | tempNoteRegistry[track][data1] = 0L; 220 | } 221 | 222 | /** 223 | * Parses instances of SysexMessage. 224 | * @param message The message to parse 225 | * @param timestamp the time at which the message was encountered in this track 226 | */ 227 | private void parseSysexMessage(SysexMessage message, long timestamp) 228 | { 229 | // Nothing to do - JFugue doesn't use sysex messages 230 | trace("SysexMessage received but not parsed by JFugue (doesn't use them)"); 231 | } 232 | 233 | /** 234 | * Parses instances of MetaMessage. 235 | * @param message The message to parse 236 | * @param timestamp the time at which the message was encountered in this track 237 | */ 238 | private void parseMetaMessage(MetaMessage message, long timestamp) 239 | { 240 | switch (message.getType()) 241 | { 242 | case 0x51 : parseTempo(message, timestamp); break; 243 | case 0x59 : break; // Even though we care about Key Signatures, we don't want to read one in from a MIDI file, 244 | // because the notes that we'll receive will already be adjusted for the key signature. 245 | // MIDI's Key Signature is more about notating sheet music that influencing the played notes. 246 | default : break; 247 | } 248 | // Nothing to do - JFugue doesn't use sysex messages 249 | trace("MetaMessage received but not parsed by JFugue (doesn't use them)"); 250 | } 251 | 252 | private void parseTempo(MetaMessage message, long timestamp) 253 | { 254 | int beatsPerMinute = TimeFactor.parseMicrosecondsPerBeat(message, timestamp); 255 | trace("Tempo Event, bpm = ",beatsPerMinute); 256 | fireTimeEvent(new Time(timestamp)); 257 | fireTempoEvent(new Tempo(beatsPerMinute)); 258 | this.tempo = beatsPerMinute; 259 | } 260 | } -------------------------------------------------------------------------------- /src/org/jfugue/Parser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JFugue - API for Music Programming 3 | * Copyright (C) 2003-2008 David Koelle 4 | * 5 | * http://www.jfugue.org 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | * 21 | */ 22 | 23 | package org.jfugue; 24 | 25 | import java.util.EventListener; 26 | 27 | import javax.swing.event.EventListenerList; 28 | 29 | /** 30 | * You may notice that there is no parse() method in the Parser class! 31 | * That's because the parse() method may take any type of parameter, as 32 | * well as any number of parameters, so it isn't something that can 33 | * declared ahead of time. 34 | * 35 | * @author David Koelle 36 | * 37 | */ 38 | public class Parser 39 | { 40 | public Parser() 41 | { 42 | progressListenerList = new EventListenerList(); 43 | listenerList = new EventListenerList (); 44 | 45 | // The Parser could add itself as a ParserProgressListener. 46 | } 47 | 48 | // Logging methods 49 | /////////////////////////////////////////// 50 | 51 | /** Pass this value to setTracing( ) to turn tracing off. Tracing is off by default. */ 52 | public static final int TRACING_OFF = 0; 53 | 54 | /** Pass this value to setTracing( ) to turn tracing on. Tracing is off by default. */ 55 | public static final int TRACING_ON = 1; 56 | 57 | private int tracing = TRACING_OFF; 58 | 59 | /** 60 | * Turns tracing on or off. If you're having trouble with your music string, 61 | * or if you've added new tokens to the parser, turn tracing on to make sure 62 | * that your new tokens are parsed correctly. 63 | * @param tracing the state of tracing - on or off 64 | */ 65 | public void setTracing(int tracing) 66 | { 67 | this.tracing = tracing; 68 | } 69 | 70 | /** 71 | * Returns the current state of tracing. 72 | * @return the state of tracing 73 | */ 74 | public int getTracing() 75 | { 76 | return this.tracing; 77 | } 78 | 79 | /** 80 | * Displays the passed String. 81 | * @param s the String to display 82 | */ 83 | protected void trace(Object... sentenceFragments) 84 | { 85 | if (TRACING_ON == getTracing()) 86 | { 87 | StringBuilder buddy = new StringBuilder(); 88 | for (int i=0; i < sentenceFragments.length; i++) 89 | { 90 | buddy.append(sentenceFragments[i]); 91 | } 92 | 93 | System.out.println(buddy.toString()); 94 | } 95 | } 96 | 97 | // 98 | // ParserProgressListener methods 99 | ///////////////////////////////////////////////////////////////////////// 100 | 101 | /** List of ParserProgressListeners */ 102 | protected EventListenerList progressListenerList; 103 | 104 | /** 105 | * Adds a ParserListener. The listener will receive events when the parser 106 | * interprets music string tokens. 107 | * 108 | * @param listener the listener that is to be notified of parser events 109 | */ 110 | public void addParserProgressListener(ParserProgressListener l) 111 | { 112 | progressListenerList.add (ParserProgressListener.class, l); 113 | } 114 | 115 | /** 116 | * Removes a ParserListener. 117 | * 118 | * @param listener the listener to remove 119 | */ 120 | public void removeParserProgressListener(ParserProgressListener l) 121 | { 122 | progressListenerList.remove (ParserProgressListener.class, l); 123 | } 124 | 125 | protected void clearParserProgressListeners() 126 | { 127 | EventListener[] l = progressListenerList.getListeners (ParserProgressListener.class); 128 | int numListeners = l.length; 129 | for (int i = 0; i < numListeners; i++) { 130 | progressListenerList.remove (ParserProgressListener.class, (ParserProgressListener)l[i]); 131 | } 132 | } 133 | 134 | /** Tells all ParserProgressListener interfaces that progress has occurred. */ 135 | protected void fireProgressReported(String description, long partComplete, long whole) 136 | { 137 | Object[] listeners = progressListenerList.getListenerList (); 138 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 139 | if (listeners[i] == ParserProgressListener.class) { 140 | ((ParserProgressListener)listeners[i + 1]).progressReported(description, partComplete, whole); 141 | } 142 | } 143 | } 144 | 145 | // 146 | // ParserListener methods 147 | ///////////////////////////////////////////////////////////////////////// 148 | 149 | /** List of ParserListeners */ 150 | protected EventListenerList listenerList; 151 | 152 | /** 153 | * Adds a ParserListener. The listener will receive events when the parser 154 | * interprets music string tokens. 155 | * 156 | * @param listener the listener that is to be notified of parser events 157 | */ 158 | public void addParserListener(ParserListener l) 159 | { 160 | listenerList.add (ParserListener.class, l); 161 | } 162 | 163 | /** 164 | * Removes a ParserListener. 165 | * 166 | * @param listener the listener to remove 167 | */ 168 | public void removeParserListener(ParserListener l) 169 | { 170 | listenerList.remove (ParserListener.class, l); 171 | } 172 | 173 | protected void clearParserListeners() 174 | { 175 | EventListener[] l = listenerList.getListeners (ParserListener.class); 176 | int numListeners = l.length; 177 | for (int i = 0; i < numListeners; i++) { 178 | listenerList.remove (ParserListener.class, (ParserListener)l[i]); 179 | } 180 | } 181 | 182 | /** Tells all ParserListeners that a voice event has been parsed. */ 183 | protected void fireVoiceEvent(Voice event) 184 | { 185 | Object[] listeners = listenerList.getListenerList (); 186 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 187 | if (listeners[i] == ParserListener.class) { 188 | ((ParserListener)listeners[i + 1]).voiceEvent(event); 189 | } 190 | } 191 | } 192 | 193 | /** Tells all ParserListeners that a tempo event has been parsed. */ 194 | protected void fireTempoEvent(Tempo event) 195 | { 196 | Object[] listeners = listenerList.getListenerList (); 197 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 198 | if (listeners[i] == ParserListener.class) { 199 | ((ParserListener)listeners[i + 1]).tempoEvent(event); 200 | } 201 | } 202 | } 203 | 204 | /** Tells all ParserListeners that an instrument event has been parsed. */ 205 | protected void fireInstrumentEvent(Instrument event) 206 | { 207 | Object[] listeners = listenerList.getListenerList (); 208 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 209 | if (listeners[i] == ParserListener.class) { 210 | ((ParserListener)listeners[i + 1]).instrumentEvent(event); 211 | } 212 | } 213 | } 214 | 215 | /** Tells all ParserListeners that a layer event has been parsed. */ 216 | protected void fireLayerEvent(Layer event) 217 | { 218 | Object[] listeners = listenerList.getListenerList (); 219 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 220 | if (listeners[i] == ParserListener.class) { 221 | ((ParserListener)listeners[i + 1]).layerEvent(event); 222 | } 223 | } 224 | } 225 | 226 | /** Tells all ParserListeners that a time event has been parsed. */ 227 | protected void fireTimeEvent(Time event) 228 | { 229 | Object[] listeners = listenerList.getListenerList (); 230 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 231 | if (listeners[i] == ParserListener.class) { 232 | ((ParserListener)listeners[i + 1]).timeEvent(event); 233 | } 234 | } 235 | } 236 | 237 | /** Tells all ParserListeners that a key signature event has been parsed. */ 238 | protected void fireKeySignatureEvent(KeySignature event) 239 | { 240 | Object[] listeners = listenerList.getListenerList (); 241 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 242 | if (listeners[i] == ParserListener.class) { 243 | ((ParserListener)listeners[i + 1]).keySignatureEvent(event); 244 | } 245 | } 246 | } 247 | 248 | /** Tells all ParserListeners that a measure event has been parsed. */ 249 | protected void fireMeasureEvent(Measure event) 250 | { 251 | Object[] listeners = listenerList.getListenerList (); 252 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 253 | if (listeners[i] == ParserListener.class) { 254 | ((ParserListener)listeners[i + 1]).measureEvent(event); 255 | } 256 | } 257 | } 258 | 259 | /** Tells all ParserListeners that a controller event has been parsed. */ 260 | protected void fireControllerEvent(Controller event) 261 | { 262 | Object[] listeners = listenerList.getListenerList (); 263 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 264 | if (listeners[i] == ParserListener.class) { 265 | ((ParserListener)listeners[i + 1]).controllerEvent(event); 266 | } 267 | } 268 | } 269 | 270 | /** Tells all ParserListeners that a controller event has been parsed. */ 271 | protected void fireChannelPressureEvent(ChannelPressure event) 272 | { 273 | Object[] listeners = listenerList.getListenerList (); 274 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 275 | if (listeners[i] == ParserListener.class) { 276 | ((ParserListener)listeners[i + 1]).channelPressureEvent(event); 277 | } 278 | } 279 | } 280 | 281 | /** Tells all ParserListeners that a controller event has been parsed. */ 282 | protected void firePolyphonicPressureEvent(PolyphonicPressure event) 283 | { 284 | Object[] listeners = listenerList.getListenerList (); 285 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 286 | if (listeners[i] == ParserListener.class) { 287 | ((ParserListener)listeners[i + 1]).polyphonicPressureEvent(event); 288 | } 289 | } 290 | } 291 | 292 | /** Tells all ParserListeners that a controller event has been parsed. */ 293 | protected void firePitchBendEvent(PitchBend event) 294 | { 295 | Object[] listeners = listenerList.getListenerList (); 296 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 297 | if (listeners[i] == ParserListener.class) { 298 | ((ParserListener)listeners[i + 1]).pitchBendEvent(event); 299 | } 300 | } 301 | } 302 | 303 | /** Tells all ParserListeners that a note event has been parsed. */ 304 | protected void fireNoteEvent(Note event) 305 | { 306 | Object[] listeners = listenerList.getListenerList (); 307 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 308 | if (listeners[i] == ParserListener.class) { 309 | ((ParserListener)listeners[i + 1]).noteEvent(event); 310 | } 311 | } 312 | } 313 | 314 | /** Tells all ParserListeners that a sequential note event has been parsed. */ 315 | protected void fireSequentialNoteEvent(Note event) 316 | { 317 | Object[] listeners = listenerList.getListenerList (); 318 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 319 | if (listeners[i] == ParserListener.class) { 320 | ((ParserListener)listeners[i + 1]).sequentialNoteEvent(event); 321 | } 322 | } 323 | } 324 | 325 | /** Tells all ParserListeners that a parallel note event has been parsed. */ 326 | protected void fireParallelNoteEvent(Note event) 327 | { 328 | Object[] listeners = listenerList.getListenerList (); 329 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 330 | if (listeners[i] == ParserListener.class) { 331 | ((ParserListener)listeners[i + 1]).parallelNoteEvent(event); 332 | } 333 | } 334 | } 335 | 336 | // 337 | // End ParserListener methods 338 | ///////////////////////////////////////////////////////////////////////// 339 | } -------------------------------------------------------------------------------- /src/org/warp/midito3d/music/mp3/Mp3Parser.java: -------------------------------------------------------------------------------- 1 | package org.warp.midito3d.music.mp3; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics; 5 | import java.awt.image.BufferedImage; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.nio.ByteBuffer; 10 | import java.nio.DoubleBuffer; 11 | import java.nio.channels.Channels; 12 | import java.nio.channels.ReadableByteChannel; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | 16 | import javax.imageio.ImageIO; 17 | import javax.sound.sampled.*; 18 | 19 | import org.jtransforms.fft.DoubleFFT_1D; 20 | import org.warp.midito3d.music.Music; 21 | 22 | import javazoom.jl.decoder.Bitstream; 23 | import javazoom.jl.decoder.BitstreamException; 24 | import javazoom.jl.decoder.Decoder; 25 | import javazoom.jl.decoder.DecoderException; 26 | import javazoom.jl.decoder.Header; 27 | import javazoom.jl.decoder.Obuffer; 28 | import javazoom.jl.decoder.SampleBuffer; 29 | 30 | public class Mp3Parser { 31 | 32 | private final static int actuatorsCount = 4; 33 | 34 | public static Mp3Music loadFrom(String file, boolean debug) throws UnsupportedAudioFileException, IOException, DecoderException, BitstreamException { 35 | File filename = new File(file); 36 | Clip c = new Clip(filename); 37 | final int length = c.getFrameCount(); 38 | final int freqSamples = c.getFrameFreqSamples(); 39 | 40 | double durationInSeconds = c.getAudio().getFrameLength() / c.getAudio().getFormat().getFrameRate()/c.getAudio().getFormat().getFrameSize(); 41 | 42 | double[][] freqs = new double[actuatorsCount][length]; 43 | 44 | double max_frequency_power = 0; 45 | double max_frequency_index = 0; 46 | for (int frameIndex = 0; frameIndex < length; frameIndex++) { 47 | Frame f = c.getFrame(frameIndex); 48 | for (int frequencyIndex = 0; frequencyIndex < freqSamples; frequencyIndex++) { 49 | double frequencyPower = Math.abs(f.getReal(frequencyIndex)); 50 | if (frequencyPower > max_frequency_power) { 51 | max_frequency_power = frequencyPower; 52 | } 53 | if (frequencyIndex > max_frequency_index) { 54 | max_frequency_index = frequencyIndex; 55 | } 56 | } 57 | } 58 | System.out.println("MAX Frequency: " + ((double)max_frequency_index * (double)c.getSpectralScale() / (double)freqSamples)); 59 | System.out.println("MAX Frequency Power: " + max_frequency_power); 60 | final double thresold_frequency_power = max_frequency_power * 0.6d; 61 | 62 | for (int frameIndex = 0; frameIndex < length; frameIndex++) { 63 | //Get max frequencies 64 | double[] max_freqs_power = new double[actuatorsCount]; 65 | double[] max_freqs_index = new double[actuatorsCount]; 66 | for (int i = 0; i < actuatorsCount; i++) { 67 | max_freqs_power[i] = 0; 68 | max_freqs_index[i] = 0; 69 | } 70 | Frame f = c.getFrame(frameIndex); 71 | /* 72 | for (int frequencyIndex = 0; frequencyIndex < freqSamples; frequencyIndex++) { 73 | for (int k = 0; k < actuatorsCount; k++) { 74 | double frequencyPower = Math.abs(f.getReal(frequencyIndex)); 75 | if (frequencyPower > thresold_frequency_power) { 76 | if (frequencyPower > max_freqs_power[k]) { 77 | boolean done = false; 78 | for (int l = 0; l < actuatorsCount; l++) { 79 | if ((double)Math.abs(frequencyIndex - max_freqs[l]) * (double)c.getSpectralScale() / ((double)freqSamples) <= 10d) { // se le note sono simili (+-10Hz) fai la media e trattale come una sola 80 | System.out.println("Joining frequencies " + ((double)frequencyIndex * (double)c.getSpectralScale() / ((double)freqSamples)) + "Hz and " + ((double)max_freqs[l] * (double)c.getSpectralScale() / ((double)freqSamples)) + "Hz"); 81 | if (frequencyIndex > max_freqs[l]) { 82 | max_freqs[l] = frequencyIndex; 83 | max_freqs_power[l] = frequencyPower > max_freqs_power[l] ? frequencyPower : max_freqs_power[l]; 84 | } 85 | 86 | //alternativa: 87 | //max_freqs[l] = (frequencyIndex * frequencyPower + max_freqs[l] * max_freqs_power[l]) / (frequencyPower + max_freqs_power[l]); // media tra le due note simili 88 | //max_freqs_power[l] = (frequencyPower + max_freqs_power[l]) / 2; // media tra le due note simili 89 | 90 | done = true; 91 | break; 92 | } 93 | } 94 | if (!done) { 95 | for (int j = actuatorsCount - 2; j >= k; j--) { 96 | max_freqs_power[j] = max_freqs_power[j+1]; 97 | max_freqs[j] = max_freqs[j+1]; 98 | } 99 | max_freqs_power[k] = frequencyPower; 100 | max_freqs[k] = ((double)frequencyIndex) * c.getSpectralScale() / ((double)freqSamples); 101 | break; 102 | } 103 | } 104 | } 105 | } 106 | } 107 | */ 108 | for (int frequencyIndex = 0; frequencyIndex < freqSamples; frequencyIndex++) { 109 | for (int k = 0; k < actuatorsCount; k++) { 110 | double frequencyPower = Math.abs(f.getReal(frequencyIndex)); 111 | if (frequencyPower > thresold_frequency_power) { 112 | if (frequencyIndex > max_freqs_index[k]) { 113 | boolean done = false; 114 | for (int l = 0; l < actuatorsCount; l++) { 115 | if ((double)Math.abs(frequencyIndex - max_freqs_index[l]) * (double)c.getSpectralScale() / ((double)freqSamples) <= 10d) { // se le note sono simili (+-10Hz) fai la media e trattale come una sola 116 | System.out.println("Joining frequencies " + ((double)frequencyIndex * (double)c.getSpectralScale() / ((double)freqSamples)) + "Hz and " + ((double)max_freqs_index[l] * (double)c.getSpectralScale() / ((double)freqSamples)) + "Hz"); 117 | if (frequencyIndex > max_freqs_index[l]) { 118 | max_freqs_index[l] = frequencyIndex; 119 | max_freqs_power[l] = frequencyPower > max_freqs_power[l] ? frequencyPower : max_freqs_power[l]; 120 | } 121 | done = true; 122 | break; 123 | } 124 | } 125 | if (!done) { 126 | for (int j = actuatorsCount - 2; j >= k; j--) { 127 | max_freqs_power[j] = max_freqs_power[j+1]; 128 | max_freqs_index[j] = max_freqs_index[j+1]; 129 | } 130 | max_freqs_power[k] = frequencyPower; 131 | max_freqs_index[k] = frequencyIndex; 132 | break; 133 | } 134 | } 135 | } 136 | } 137 | } 138 | for (int i = 0; i < actuatorsCount; i++) { 139 | freqs[i][frameIndex] = (double)max_freqs_index[i] * (double)c.getSpectralScale() / (double)freqSamples; 140 | } 141 | } 142 | 143 | freqsArrToImg(freqs, false, 2d, 64, "freqs"); 144 | float samplesPerSecond = (float) (((double)freqs[0].length)/durationInSeconds); 145 | System.out.println("Song duration: "+durationInSeconds+"s; "); 146 | 147 | debugMusic(freqs, durationInSeconds); 148 | 149 | return new Mp3Music(freqs, samplesPerSecond, debug); 150 | } 151 | 152 | private static void debugMusic(final double[][] freqs, final double songDuration) { 153 | System.out.println("Debugging music"); 154 | for (int chan = 0; chan < 1; chan++) { 155 | final int chanF = chan; 156 | new Thread(() -> { 157 | try { 158 | createToneList(); 159 | for (int idx = 0; idx < freqs[chanF].length; idx++) { 160 | writeTone((int) (freqs[chanF][idx]), (int) (songDuration*1000d/freqs[0].length), 0.5d / (double)actuatorsCount * ((double)actuatorsCount - chanF)); 161 | //writeTone(265, 23, 0.5d); 162 | } 163 | startToneList(); 164 | System.out.println("track ended."); 165 | } catch (LineUnavailableException e) { 166 | e.printStackTrace(); 167 | } 168 | }).start(); 169 | } 170 | } 171 | 172 | private static float SAMPLE_RATE = 8000f; 173 | private static void writeTone(int hz, int msecs) throws LineUnavailableException { 174 | writeTone(hz, msecs, 0.5d); 175 | } 176 | private static SourceDataLine sdl; 177 | private static void createToneList() throws LineUnavailableException { 178 | AudioFormat af = new AudioFormat(SAMPLE_RATE,8,1,true,false); 179 | sdl = AudioSystem.getSourceDataLine(af); 180 | sdl.open(af); 181 | sdl.start(); 182 | } 183 | private static void startToneList() throws LineUnavailableException { 184 | sdl.drain(); 185 | sdl.stop(); 186 | sdl.close(); 187 | } 188 | 189 | private static void writeTone(int hz, int msecs, double vol) throws LineUnavailableException { 190 | byte[] buf = new byte[1]; 191 | for (int i=0; i < msecs*8; i++) { 192 | double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI; 193 | buf[0] = (byte)(Math.sin(angle) * 127.0 * vol); 194 | sdl.write(buf,0,1); 195 | } 196 | } 197 | 198 | private static void arrToImg(double[] input, boolean reverseY, double widthmultiplier, int height, String name) { 199 | BufferedImage debugAudioRawData = new BufferedImage((int)(input.length*widthmultiplier), height, BufferedImage.TYPE_INT_RGB); 200 | Graphics g = debugAudioRawData.getGraphics(); 201 | g.setColor(Color.black); 202 | g.clearRect(0, 0, (int)(input.length*widthmultiplier), height); 203 | g.setColor(Color.white); 204 | double min = Double.MAX_VALUE; 205 | double max = Double.MIN_VALUE; 206 | for (int i = 0; i < input.length; i++) { 207 | if (input[i] > max) { 208 | max = input[i]; 209 | } 210 | if (input[i] < min) { 211 | min = input[i]; 212 | } 213 | } 214 | System.out.println("min: "+ min + ", max: " + max); 215 | for (int i = 0; i < input.length; i++) { 216 | final int y = (int) (((input[i] - min) / (max-min)) * (double)(height - 1)); 217 | g.fillRect((int)(i*widthmultiplier), reverseY?y:((height - 1) - y), widthmultiplier>1?(int)(1*widthmultiplier):1, 1); 218 | } 219 | g.dispose(); 220 | try { 221 | ImageIO.write(debugAudioRawData, "bmp", new File("C:\\Users\\Andrea Cavalli\\Videos\\Desktop\\"+name+".bmp")); 222 | } catch (IOException e) { 223 | e.printStackTrace(); 224 | } 225 | } 226 | 227 | private static void freqsArrToImg(double[][] input, boolean reverseY, double widthmultiplier, int height, String name) { 228 | BufferedImage debugAudioRawData = new BufferedImage((int)(input[0].length*widthmultiplier), height, BufferedImage.TYPE_INT_RGB); 229 | Graphics g = debugAudioRawData.getGraphics(); 230 | g.setColor(Color.black); 231 | g.clearRect(0, 0, (int)(input[0].length*widthmultiplier), height); 232 | g.setColor(Color.white); 233 | double min = Double.MAX_VALUE; 234 | double max = Double.MIN_VALUE; 235 | for (int i = 0; i < input.length; i++) { 236 | for (int j = 0; j < input[i].length; j++) { 237 | if (input[i][j] > max) { 238 | max = input[i][j]; 239 | } 240 | if (input[i][j] < min) { 241 | min = input[i][j]; 242 | } 243 | } 244 | } 245 | System.out.println("freq min: "+ min + ", freq max: " + max); 246 | for (int i = 0; i < input.length; i++) { 247 | for (int j = 0; j < input[i].length; j++) { 248 | final int y = (int) (((input[i][j] - min) / (max-min)) * (double)(height - 1)); 249 | g.fillRect((int)(j*widthmultiplier), reverseY?y:((height - 1) - y), widthmultiplier>1?(int)(1*widthmultiplier):1, 1); 250 | } 251 | } 252 | g.dispose(); 253 | try { 254 | ImageIO.write(debugAudioRawData, "bmp", new File("C:\\Users\\Andrea Cavalli\\Videos\\Desktop\\"+name+".bmp")); 255 | } catch (IOException e) { 256 | e.printStackTrace(); 257 | } 258 | } 259 | 260 | static double[][] convertToFreq(double[] input, double sampleRate, final int simultaneousFrequencies) { 261 | int index = 0; 262 | final int step = (int)(sampleRate/2d); //2 steps per second 263 | 264 | double[][] freqs = new double[simultaneousFrequencies][(input.length+step-1)/step]; 265 | 266 | int indx = 0; 267 | while (input.length - index > 0) { 268 | int curStep; 269 | if (index + step < input.length) { 270 | curStep = step; 271 | } else { 272 | curStep = input.length - index; 273 | } 274 | 275 | double[] inputSegment = Arrays.copyOfRange(input, index, index+curStep); 276 | double[] freqsInstant = getFreq(inputSegment, sampleRate, simultaneousFrequencies); 277 | for (int i = 0; i < simultaneousFrequencies; i++) { 278 | freqs[i][indx] = freqsInstant[i]; 279 | } 280 | indx++; 281 | index+=curStep; 282 | 283 | } 284 | return freqs; 285 | } 286 | 287 | static double[] getFreq(double[] input, double sampleRate, final int simultaneousFrequencies) { 288 | final int n = input.length; 289 | 290 | //Calculate FFT 291 | DoubleFFT_1D fft = new DoubleFFT_1D(n); 292 | double[] fftData = new double[n*2]; 293 | double[] magnitude = new double[n/2]; 294 | System.arraycopy(input, 0, fftData, 0, n); 295 | fft.realForwardFull(fftData); 296 | 297 | //Calculate magnitude; 298 | for (int i = 0; i < n/2; i++) { 299 | double re = fftData[2*i]; 300 | double im = fftData[2*i+1]; 301 | magnitude[i] = Math.sqrt(re*re+im*im); 302 | } 303 | 304 | //Get frequency 305 | double[] max_magnitudes = new double[simultaneousFrequencies]; 306 | double[] max_indexes = new double[simultaneousFrequencies]; 307 | for (int i = 0; i < simultaneousFrequencies; i++) { 308 | max_magnitudes[i] = Double.NEGATIVE_INFINITY; 309 | max_indexes[i] = -1; 310 | } 311 | for (int i = 0; i < n/2; i++) { 312 | for (int k = 0; k < simultaneousFrequencies; k++) { 313 | if (magnitude[i] > max_magnitudes[k]) { 314 | for (int j = simultaneousFrequencies - 2; j >= k; j--) { 315 | max_magnitudes[j] = max_magnitudes[j+1]; 316 | max_indexes[j] = max_indexes[j+1]; 317 | } 318 | max_magnitudes[k] = magnitude[i]; 319 | max_indexes[k] = i; 320 | break; 321 | } 322 | } 323 | } 324 | 325 | //System.out.println(max_indexes * sampleRate / ((double)n)); 326 | double[] result = new double[simultaneousFrequencies]; 327 | for (int i = 0; i < simultaneousFrequencies; i++) { 328 | result[i] = max_indexes[i] * sampleRate / ((double)n); 329 | } 330 | return result; 331 | } 332 | 333 | public static double[][] realToComplex(double[] real) { 334 | double[][] complex = new double[real.length][2]; 335 | for (int i = 0; i < real.length; ++i) { 336 | complex[i][0] = real[i]; 337 | } 338 | return complex; 339 | } 340 | 341 | static double[] concatArrays(double[] input, double[] smpls) { 342 | 343 | int aLen = input.length; 344 | int bLen = smpls.length; 345 | double[] C= new double[aLen+bLen]; 346 | 347 | System.arraycopy(input, 0, C, 0, aLen); 348 | System.arraycopy(smpls, 0, C, aLen, bLen); 349 | 350 | return C; 351 | } 352 | 353 | } 354 | --------------------------------------------------------------------------------