Skip to content

Commit

Permalink
MUAHHH, how could i delete project folder with uncommited changes??? …
Browse files Browse the repository at this point in the history
…Restored script that generates readable json data from midi files. One day whole humanity will use this instead of yours looser midi format!
  • Loading branch information
klesun committed Oct 16, 2015
1 parent 0025a2b commit 879da67
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 67 deletions.
4 changes: 4 additions & 0 deletions run_script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
javac -sourcepath "./src" -d "./bin" "./src/org/shmidusic/stuff/scripts/MidiToReadableMidi.java" -cp "libs/commons-math3-3.5.jar:libs/guava-18.0.jar"
cd bin
java org.shmidusic.stuff.scripts.MidiToReadableMidi
cd ..
Binary file removed shmidusic.jar
Binary file not shown.
115 changes: 87 additions & 28 deletions src/org/shmidusic/stuff/midi/NoteGuesser.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// obvious from name, it guesses note lengths from event timestamp and unitsPerSecond

import org.apache.commons.math3.fraction.Fraction;
import org.json.JSONObject;
import org.shmidusic.Main;
import org.shmidusic.sheet_music.SheetMusic;
import org.shmidusic.sheet_music.staff.Staff;
Expand All @@ -14,6 +15,7 @@
import org.shmidusic.stuff.midi.standard_midi_file.event.*;
import org.shmidusic.stuff.tools.INote;
import org.shmidusic.stuff.tools.Logger;
import org.shmidusic.stuff.tools.Triplet;

import javax.swing.*;
import java.util.*;
Expand All @@ -39,12 +41,75 @@ public NoteGuesser(SMF smf) {
this.unitsPerSecond = smf.getPPQN();
}

public JSONObject generateMidiJson()
{
// TODO: investigate. i suspect that there are no midi-s with variable tempo and program per channel on practice
// if so - tempo should be just single double value (first event probably) - not list and no times. same with program change - just probably dict channel -> instrumentId
// cuz some files ("Banjo kazooie - Rusty Bucket Bay Duet.mid", "Terranigma - Europe (1).mid") have obviously trashy countless duplicate messages
Triplet<List<GuessingNote>, List<TempoEvent>, List<PChange>> extracted = getNotes();

return new JSONObject()
.put("division", unitsPerSecond)

.put("tempoEventList", extracted.elem2.stream()
.map(e -> new JSONObject()
.put("time", e.getTime())
.put("tempo", e.getTempo()))
.collect(Collectors.toList()).toArray())

.put("instrumentDict", extracted.elem3.stream()
.sorted(Comparator.comparing(Event::getTime))
.collect(Collectors.toMap(
PChange::getMidiChannel,
PChange::getValue,
(dup1, dup2) -> dup1 == dup2
? dup1
: (Math.random() < 0.5 ? dup1 : dup2) // ибо нехуй
)))

.put("noteList", extracted.elem1.stream()
.map(n -> new JSONObject()
.put("tune", n.tune)
.put("time", n.time)
.put("duration", n.duration)
.put("channel", n.channel))
.collect(Collectors.toList()).toArray())
;

}

public SheetMusic generateSheetMusic(Consumer<SheetMusic> rebuild)
{
SheetMusic sheetMusic = new SheetMusic();
Staff staff = sheetMusic.staffList.get(0);

Triplet<List<GuessingNote>, List<TempoEvent>, List<PChange>> extracted = getNotes();

List<GuessingNote> notes = extracted.elem1;
staff.getConfig().setTempo(extracted.elem2.stream()
.min(Comparator.comparing(TempoEvent::getTime))
.map(TempoEvent::getTempo).map(Integer.class::cast).orElse(120));
extracted.elem3.forEach(e -> staff.getConfig().channelList.get(e.getMidiChannel()).setInstrument(e.getValue()));

notes.stream().sorted((n1,n2) -> n1.time - n2.time).forEach(n -> {
/** @debug */
// System.out.println(guessPos(n.time) + " " + n.strMe());
putAt(guessPos(n.time), n, staff);

/** @debug */
// rebuild.accept(sheetMusic);
// JOptionPane.showMessageDialog(Main.window, "zhopa");
});

return sheetMusic;
}

// all delta-times are rewrited with absolute times!
private Triplet<List<GuessingNote>, List<TempoEvent>, List<PChange>> getNotes()
{
List<GuessingNote> notes = new ArrayList<>();
List<TempoEvent> tempos = new ArrayList<>();
Collection<PChange> instruments = new ArrayList<>();

for (Track track: midiFile.getTrackList()) {

Expand All @@ -64,48 +129,38 @@ public SheetMusic generateSheetMusic(Consumer<SheetMusic> rebuild)

INoteEvent noteOff = (INoteEvent)event;
opened.stream().filter(n -> n.tune == noteOff.getPitch() && n.channel == noteOff.getMidiChannel())
.findAny().ifPresent(n ->
.findAny().ifPresent(n ->
{
n.setDuration(finalTime - n.time);
notes.add(n);
opened.remove(n);
});

} else {
// handle staff config event
if (time == 0) {
consumeConfigEvent(event, staff.getConfig());
event.setTime(time);
if (event instanceof TempoEvent) {
tempos.add((TempoEvent) event);
} else if (event instanceof PChange) {
instruments.add((PChange)event);
} else {
Logger.warning("Config event not at the start of midi: " + event.getClass().getSimpleName() + " " + event.getTime() + ". Should we split Staff in future on this case?");
// apohuj
}
}
}
}

notes.stream().sorted((n1,n2) -> n1.time - n2.time).forEach(n -> {
/** @debug */
// System.out.println(guessPos(n.time) + " " + n.strMe());
putAt(guessPos(n.time), n, staff);

/** @debug */
// rebuild.accept(sheetMusic);
// JOptionPane.showMessageDialog(Main.window, "zhopa");
});
notes.sort(Comparator.comparing(GuessingNote::getTime));

return sheetMusic;
}
// some .mid files in my collections (for example 0_c5_Final Fantasy XI - Sanctuary of Zi'tah.mid.js)
// have different program events with same time and channel... i suppose in such case should be used the latter
instruments = instruments.stream()
.collect(Collectors.toMap(
i -> Arrays.asList(new Integer(i.getTime()), new Short(i.getMidiChannel())),
i -> i,
(dup1, dup2) -> dup2))
.values();

private void consumeConfigEvent(Event event, StaffConfig config)
{
if (event instanceof TempoEvent) {
tempo = (int)((TempoEvent) event).getTempo();
// config.setTempo(tempo);
} else if (event instanceof PChange) {
PChange instrument = (PChange) event;
config.channelList.get(instrument.getMidiChannel()).setInstrument(instrument.getValue());
} else {
// ...
}
return new Triplet<>(notes, tempos, new ArrayList<>(instruments));
}

private void putAt(Fraction desiredPos, INote note, Staff staff)
Expand Down Expand Up @@ -247,7 +302,7 @@ public static <T> LinkedList<T> asList(T... a) {
}

private int toUnits(Fraction length) {
return length.multiply(unitsPerSecond).intValue();
return length.multiply(unitsPerSecond * 4).intValue(); // * 4 because semibreveEventLength/division = 1/4
}

private static Fraction fr(int num, int den) {
Expand Down Expand Up @@ -287,6 +342,10 @@ private Fraction getActualLength() {
return guessLength(this.duration);
}

public int getTime() {
return this.time;
}

public String strMe() {
return "time: " + time + "; tune: " + tune + "; channel: " + channel + "; duration: " + duration + "; length: " + getActualLength();
}
Expand Down
11 changes: 8 additions & 3 deletions src/org/shmidusic/stuff/midi/standard_midi_file/SMF.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,14 @@ public void read(InputStream is)
}
*/
//Read all track chunks
for(int i = 0; i < numOfTracks; i++){
readTrackChunk( dis);
}
try {
for (int i = 0; i < numOfTracks; i++) {
readTrackChunk(dis);
}
} catch (EOFException exc) {
// 0_c5_Sailor Moon - Romance- Heartbeats on a Falling Star Night - Excerpt from SMR Symphonic Poem -.mid
System.out.println("Unexpected eof - proceeding");
}
is.close();
dis.close();
}
Expand Down
58 changes: 23 additions & 35 deletions src/org/shmidusic/stuff/scripts/MidiToReadableMidi.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
// this script takes midi file and outputs json representation of this file
// SMF class reading result

import org.shmidusic.stuff.OverridingDefaultClasses.MutableInt;
import org.shmidusic.stuff.midi.NoteGuesser;
import org.shmidusic.stuff.midi.standard_midi_file.SMF;
import org.shmidusic.stuff.midi.standard_midi_file.Track;
import org.shmidusic.stuff.midi.standard_midi_file.event.Event;
import org.json.JSONArray;
import org.json.JSONObject;
import org.klesun_model.Explain;
import org.shmidusic.stuff.tools.Logger;

import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
Expand All @@ -19,46 +24,29 @@ public class MidiToReadableMidi
{
public static void main(String[] args) throws IOException
{
File source = new File("Z:/Dropbox/midiCollection/random_good_stuff/morrowind_main.mid");
File destination = new File("Z:/Dropbox/midiCollection/random_good_stuff/morrowind_main.mid.js");
List<String> dirNames = Arrays.asList(".", "watched", "random_good_stuff");
String base = "/home/klesun/Dropbox/midiCollection/";

SMF smf = new SMF();
smf.read(new FileInputStream(source));
MutableInt counter = new MutableInt(0);

JSONObject result = new JSONObject();
result.put("division", smf.getPPQN());
JSONArray trackList = new JSONArray();
dirNames.stream()
.map(d -> base + d).map(File::new).flatMap(f -> Arrays.stream(f.listFiles()))
.filter(f -> f.getPath().endsWith(".mid")).forEach(source -> {

BiFunction<Integer, String, String> space = (width, text) ->
(width > text.length())
? new String(new char[width - text.length()]).replace("\0", " ") + text
: text.substring(0, width);
/** @debug */
System.out.println(counter.incr() + " Processing " + source);

for (Track track: smf.getTrackList()) {
JSONArray eventList = new JSONArray();
for (Event event: track.getEvtList()) {
File destination = new File("/home/klesun/Dropbox/midiCollection_smf/" + source.getName() + ".js"); // hoping that file names does not repeat...

ByteArrayOutputStream bos = new ByteArrayOutputStream(4);
DataOutputStream os = new DataOutputStream(bos);
event.write(os);
byte[] byteArray = bos.toByteArray();
String bytes = IntStream.range(0, byteArray.length).map(n -> byteArray[n] & 0xFF).boxed()
.map(n -> space.apply(3, n + ""))
.collect(Collectors.joining(", "));

eventList.put(new JSONObject()
.put("eventName", space.apply(10, event.getClass().getSimpleName()))
.put("deltaTime", space.apply(5, "" + event.getTime()))
.put("bytes", bytes)
);
}

trackList.put(new JSONObject().put("eventList", eventList));
}

result.put("trackList", trackList);

writeTextToFile(result.toString(2), destination);
try {
SMF smf = new SMF();
smf.read(new FileInputStream(source));
JSONObject midJs = new NoteGuesser(smf).generateMidiJson();
writeTextToFile(midJs.toString(2), destination);
} catch (Exception exc) {
Logger.fatal(exc, "Unexpected exception while processing " + source.getName());
}
});
}

private static Explain writeTextToFile(String text, File f) throws IOException
Expand Down
2 changes: 1 addition & 1 deletion src/org/shmidusic/stuff/tools/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class Logger {

final private static String PRE_FATAL_BACKUP_FOLDER = "savedJustBeforeFatal/";

public static int getFatal(String msg) {
public static Integer getFatal(String msg) {
fatal(msg);
return -100;
}
Expand Down
15 changes: 15 additions & 0 deletions src/org/shmidusic/stuff/tools/Triplet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.shmidusic.stuff.tools;

public class Triplet<T1, T2, T3>
{
final public T1 elem1;
final public T2 elem2;
final public T3 elem3;

public Triplet(T1 elem1, T2 elem2, T3 elem3)
{
this.elem1 = elem1;
this.elem2 = elem2;
this.elem3 = elem3;
}
}

0 comments on commit 879da67

Please sign in to comment.