package org.open2jam.parsers; import java.io.*; import java.util.Map.Entry; import java.util.*; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.open2jam.parsers.utils.CharsetDetector; import org.open2jam.parsers.utils.Filters; import org.open2jam.parsers.utils.Logger; import org.open2jam.parsers.utils.SampleData; class BMSParser { private static final Pattern note_line = Pattern.compile("^#(\\d\\d\\d)(\\d\\d):(.+)$"); private static final Pattern bpm_line = Pattern.compile("^#BPM(\\w\\w)\\s+(.+)$"); private static final Pattern stop_line = Pattern.compile("^#STOP(\\w\\w)\\s+(.+)$"); private static final FileFilter bms_filter = new FileFilter(){ public boolean accept(File f){ String s = f.getName().toLowerCase(); return (!f.isDirectory()) && (s.endsWith(".bms") || s.endsWith(".bme") || s.endsWith(".bml") || s.endsWith(".pms")); } }; public static boolean canRead(File f) { if(!f.isDirectory())return false; File[] bms = f.listFiles(bms_filter); return bms.length > 0; } public static ChartList parseFile(File file) { ChartList list = new ChartList(); list.source_file = file; File[] bms_files = file.listFiles(bms_filter); for (File bms_file : bms_files) { try { BMSChart chart = parseBMSHeader(bms_file); if (chart != null) list.add(chart); } catch (Exception e) { Logger.global.log(Level.WARNING, "{0}", e); } } Collections.sort(list); if (list.isEmpty()) return null; return list; } private static BMSChart parseBMSHeader(File f) throws IOException { String charset = CharsetDetector.analyze(f); BMSChart chart = new BMSChart(); chart.source = f; BufferedReader r; try{ r = new BufferedReader(new InputStreamReader(new FileInputStream(f),charset)); }catch(FileNotFoundException e){ Logger.global.log(Level.WARNING, "File {0} not found !!", f.getName()); return null; }catch(UnsupportedEncodingException e2){ Logger.global.log(Level.WARNING, "Encoding [{0}] not supported !", charset); r = new BufferedReader(new FileReader(f)); } String line; StringTokenizer st; chart.sample_index = new HashMap<Integer, String>(); int max_key = 0, max_measure = 0, total_notes = 0, scratch_notes = 0; try{ while((line = r.readLine()) != null) { line = line.trim(); if(!line.startsWith("#"))continue; st = new StringTokenizer(line); String cmd = st.nextToken().toUpperCase(); try{ if(cmd.equals("#PLAYLEVEL")){ chart.level = Integer.parseInt(st.nextToken()); continue; } if(cmd.equals("#RANK")){ //int rank = Integer.parseInt(st.nextToken()); continue; } if(cmd.equals("#TITLE")){ chart.title = st.nextToken("").trim(); continue; } if(cmd.equals("#ARTIST")){ chart.artist = st.nextToken("").trim(); continue; } if(cmd.equals("#GENRE")){ chart.genre = st.nextToken("").trim(); continue; } if(cmd.equals("#PLAYER")){ //TODO Add player x support int player = Integer.parseInt(st.nextToken()); if(player != 1) { Logger.global.log(Level.WARNING, "#PLAYER{0} not supported @ {1}",new Object[] {player, f.getName()}); return null; } continue; } if(cmd.equals("#BPM")){ chart.bpm = Double.parseDouble(st.nextToken()); continue; } if(cmd.equals("#LNTYPE")){ chart.lntype = Integer.parseInt(st.nextToken()); continue; } if(cmd.equals("#LNOBJ")){ chart.lnobj = Integer.parseInt(st.nextToken(), 36); } if(cmd.equals("#STAGEFILE")){ chart.image_cover = null; File cover = new File(f.getParent(), st.nextToken("").trim()); if(cover.exists()) { chart.cover_name = cover.getName(); chart.image_cover = cover; } else { String target = cover.getName(); int idx = target.lastIndexOf('.'); if(idx > 0) { target = target.substring(0, idx); } for(File ff : chart.source.getParentFile().listFiles(Filters.imageFilter)) { String s = ff.getName(); idx = s.lastIndexOf('.'); if (idx > 0) { s = s.substring(0, idx); } if (target.equalsIgnoreCase(s)) { chart.cover_name = ff.getName(); chart.image_cover = ff; break; } } } } if(cmd.startsWith("#WAV")){ int id = Integer.parseInt(cmd.replaceFirst("#WAV",""), 36); String name = st.nextToken("").trim(); chart.sample_index.put(id, name); continue; } if(cmd.startsWith("#BMP")){ int id = Integer.parseInt(cmd.replaceFirst("#BMP",""), 36); String name = st.nextToken("").trim(); chart.bga_index.put(id, name); continue; } Matcher note_match = note_line.matcher(cmd); if(note_match.find()){ int measure = Integer.parseInt(note_match.group(1)); int channel = Integer.parseInt(note_match.group(2)); if(channel > 50)channel -= 40; if(channel > max_key)max_key = channel; if(measure >= max_measure) max_measure = measure; switch(channel){ case 16:case 11:case 12:case 13: case 14:case 15:case 18:case 19: String[] notes = note_match.group(3).split("(?<=\\G.{2})"); for(String n : notes){ if(!n.equals("00")){ total_notes++; if(channel == 16)scratch_notes++; } } } } }catch(NoSuchElementException ignored){} catch(NumberFormatException e){ Logger.global.log(Level.WARNING, "unparsable number @ {0} on file {1}", new Object[]{cmd, f.getName()}); } } }catch(IOException e){ Logger.global.log(Level.WARNING, "IO exception on file parsing ! {0}", e.getMessage()); } chart.notes = total_notes; chart.duration = (int) Math.round((240 * max_measure)/chart.bpm); if(max_key == 18 && scratch_notes > 0){ chart.o2mania_style = true; } else { chart.o2mania_style = false; chart.notes -= scratch_notes; } switch(max_key) { case 15: case 16: chart.keys = 5; break; case 18: // o2mania case 19: chart.keys = 7; break; case 25: case 26: chart.keys = 10; break; case 27: case 28: chart.keys = 14; break; default: Logger.global.log(Level.WARNING, "Unknown key number {0} on file {1}", new Object[]{max_key, f.getName()}); } return chart; } public static EventList parseChart(BMSChart chart) { EventList event_list = new EventList(); BufferedReader r; String line; try{ r = new BufferedReader(new FileReader(chart.source)); }catch(FileNotFoundException e){ Logger.global.log(Level.WARNING, "File {0} not found !!", chart.source); return null; } HashMap<Integer, Double> bpm_map = new HashMap<Integer, Double>(); HashMap<Integer, Integer> stop_map = new HashMap<Integer, Integer>(); HashMap<Integer, Boolean> ln_buffer = new HashMap<Integer, Boolean>(); HashMap<Integer, Event> lnobj_buffer = new HashMap<Integer, Event>(); //This will help us to sort the lines by measure Map<Integer, List<String>> lines = new TreeMap<Integer, List<String>>(); try { while ((line = r.readLine()) != null) { line = line.trim().toUpperCase(); if (!line.startsWith("#")) { continue; } Matcher matcher = note_line.matcher(line); if (!matcher.find()) { if (line.startsWith("#BPM")) { Matcher bpm_match = bpm_line.matcher(line); if (bpm_match.find()) { int code = Integer.parseInt(bpm_match.group(1), 36); double value = Double.parseDouble(bpm_match.group(2).replace(",", ".")); bpm_map.put(code, value); } } else if (line.startsWith(("#STOP"))) { Matcher stop_match = stop_line.matcher(line); if (stop_match.find()) { int code = Integer.parseInt(stop_match.group(1), 36); int value = Integer.parseInt(stop_match.group(2)); stop_map.put(code, value); } } continue; } int measure = Integer.parseInt(matcher.group(1)); //Let's add the line if (!lines.containsKey(measure)) { lines.put(measure, new ArrayList<String>()); } lines.get(measure).add(line); } } catch (IOException ex) { Logger.global.log(Level.SEVERE, "{0}", ex); } Iterator<List<String>> it = lines.values().iterator(); //now iterate by all the lines and add the events while(it.hasNext()) { List<String> lstr = it.next(); for(String l : lstr) { Matcher matcher = note_line.matcher(l); if(!matcher.find()) continue; int measure = Integer.parseInt(matcher.group(1)); int channel = Integer.parseInt(matcher.group(2)); if (channel == 2) { // time signature double value = Double.parseDouble(matcher.group(3).replace(",", ".")); event_list.add(new Event(Event.Channel.TIME_SIGNATURE, measure, 0, value, Event.Flag.NONE)); continue; } String[] events = matcher.group(3).split("(?<=\\G.{2})"); if (channel == 3) { //INLINE BPM CHANGE for (int i = 0; i < events.length; i++) { int value = Integer.parseInt(events[i], 16); if (value == 0) { continue; } double p = ((double) i) / events.length; event_list.add(new Event(Event.Channel.BPM_CHANGE, measure, p, value, Event.Flag.NONE)); } continue; } else if (channel == 8) { //BPM TAG BPM CHANGE for (int i = 0; i < events.length; i++) { if (events[i].equals("00")) { continue; } double value = bpm_map.get(Integer.parseInt(events[i], 36)); double p = ((double) i) / events.length; event_list.add(new Event(Event.Channel.BPM_CHANGE, measure, p, value, Event.Flag.NONE)); } continue; } else if (channel == 4) { //BGA DATA for (int i = 0; i < events.length; i++) { int value = Integer.parseInt(events[i], 36); if (value == 0) { continue; } double p = ((double) i) / events.length; event_list.add(new Event(Event.Channel.BGA, measure, p, value, Event.Flag.NONE)); } continue; } else if (channel == 9) { //STOP DATA for (int i = 0; i < events.length; i++) { if (events[i].equals("00")) { continue; } double value = stop_map.get(Integer.parseInt(events[i], 36)); double p = ((double) i) / events.length; event_list.add(new Event(Event.Channel.STOP, measure, p, value, Event.Flag.NONE)); } continue; } Event.Channel ec; if (chart.o2mania_style) { switch (channel) { // normal notes case 16: channel = 11; break; case 11: channel = 12; break; case 12: channel = 13; break; case 13: channel = 14; break; case 14: channel = 15; break; case 15: channel = 18; break; case 18: channel = 19; break; // long notes case 56: channel = 51; break; case 51: channel = 52; break; case 52: channel = 53; break; case 53: channel = 54; break; case 54: channel = 55; break; case 55: channel = 58; break; case 58: channel = 59; break; } } switch (channel) { case 1: ec = Event.Channel.AUTO_PLAY; break; case 11: case 51: ec = Event.Channel.NOTE_1; break; case 12: case 52: ec = Event.Channel.NOTE_2; break; case 13: case 53: ec = Event.Channel.NOTE_3; break; case 14: case 54: ec = Event.Channel.NOTE_4; break; case 15: case 55: ec = Event.Channel.NOTE_5; break; case 18: case 58: ec = Event.Channel.NOTE_6; break; case 19: case 59: ec = Event.Channel.NOTE_7; break; case 16: case 56: ec = Event.Channel.NOTE_SC; break; default: continue; } for (int i = 0; i < events.length; i++) { int value = Integer.parseInt(events[i], 36); double p = ((double) i) / events.length; if (channel > 50) { Boolean b = ln_buffer.get(channel); if (b != null && b) { if (chart.lntype == 2) { if (value == 0) { event_list.add(new Event(ec, measure, p, value, Event.Flag.RELEASE)); ln_buffer.put(channel, false); } } else { if (value > 0) { event_list.add(new Event(ec, measure, p, value, Event.Flag.RELEASE)); ln_buffer.put(channel, false); } } } else { if (value > 0) { ln_buffer.put(channel, true); event_list.add(new Event(ec, measure, p, value, Event.Flag.HOLD)); } } } else { if (value == 0) { continue; } Event e = new Event(ec, measure, p, value, Event.Flag.NONE); if(chart.lnobj != 0){ if(value == chart.lnobj){ e.flag = Event.Flag.RELEASE; lnobj_buffer.get(channel).flag = Event.Flag.HOLD; lnobj_buffer.put(channel, null); }else{ lnobj_buffer.put(channel, e); } } event_list.add(e); } } } } Collections.sort(event_list); return event_list; } public static HashMap<Integer, SampleData> getSamples(BMSChart chart) { HashMap<Integer, SampleData> samples = new HashMap<Integer, SampleData>(); List<File> files = Arrays.asList(chart.source.getParentFile().listFiles(Filters.sampleFilter)); Iterator<Entry<Integer, String>> it_samples = chart.sample_index.entrySet().iterator(); while(it_samples.hasNext()) { Entry<Integer, String> entry = it_samples.next(); try { Iterator<File> it_files = files.iterator(); while(it_files.hasNext()) { File f = it_files.next(); String sn = entry.getValue().toLowerCase(); String fn = f.getName().toLowerCase(); String ext = fn.substring(fn.lastIndexOf("."), fn.length()); sn = sn.substring(0, sn.lastIndexOf(".")); fn = fn.substring(0,fn.lastIndexOf(".")); if(sn.equals(fn)) { SampleData.Type t; if (ext.equals(".wav")) t = SampleData.Type.WAV; else if (ext.equals(".ogg")) t = SampleData.Type.OGG; else if (ext.equals(".mp3")) t = SampleData.Type.MP3; else { //not a music file so continue continue; } samples.put(entry.getKey(), new SampleData(new FileInputStream(f), t, f.getName())); break; } } } catch (IOException ex) { Logger.global.log(Level.SEVERE, "{0}", ex); } } return samples; } static Map<Integer, File> getImages(BMSChart chart) { HashMap<Integer, File> images = new HashMap<Integer, File>(); List<File> files = Arrays.asList(chart.source.getParentFile().listFiles(Filters.imageFilter)); Iterator<Entry<Integer, String>> it_images = chart.bga_index.entrySet().iterator(); while(it_images.hasNext()) { Entry<Integer, String> entry = it_images.next(); Iterator<File> it_files = files.iterator(); while(it_files.hasNext()) { File f = it_files.next(); String in = entry.getValue().toLowerCase(); String fn = f.getName().toLowerCase(); String ext = fn.substring(fn.lastIndexOf("."), fn.length()); in = in.substring(0, in.lastIndexOf(".")); fn = fn.substring(0,fn.lastIndexOf(".")); if(in.equals(fn)) { images.put(entry.getKey(), f); break; } } } return images; } static boolean hasVideo(BMSChart chart) { List<File> files = Arrays.asList(chart.source.getParentFile().listFiles(Filters.videoFilter)); Iterator<Entry<Integer, String>> it_images = chart.bga_index.entrySet().iterator(); while(it_images.hasNext()) { Entry<Integer, String> entry = it_images.next(); Iterator<File> it_files = files.iterator(); while(it_files.hasNext()) { File f = it_files.next(); String in = entry.getValue().toLowerCase(); String fn = f.getName().toLowerCase(); String ext = fn.substring(fn.lastIndexOf("."), fn.length()); in = in.substring(0, in.lastIndexOf(".")); fn = fn.substring(0,fn.lastIndexOf(".")); if(in.equals(fn)) { chart.video = f; return true; } } } return false; } }