package org.open2jam.parsers;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collections;
import java.util.logging.Level;
import org.open2jam.parsers.utils.ByteHelper;
import org.open2jam.parsers.utils.Logger;
class OJNParser
{
private static final String genre_map[] = {"Ballad","Rock","Dance","Techno","Hip-hop",
"Soul/R&B","Jazz","Funk","Classical","Traditional","Etc"};
/** the signature that appears at offset 4, "ojn\0" in little endian */
private static final int OJN_SIGNATURE = 0x006E6A6F;
public static boolean canRead(File file)
{
return file.getName().toLowerCase().endsWith(".ojn");
}
public static ChartList parseFile(File file)
{
ByteBuffer buffer;
RandomAccessFile f;
try{
f = new RandomAccessFile(file.getAbsolutePath(),"r");
buffer = f.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, 300);
}catch(IOException e){
Logger.global.log(Level.WARNING, "IO exception on reading OJN file {0}", file.getName());
return null;
}
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
OJNChart easy = new OJNChart();
OJNChart normal = new OJNChart();
OJNChart hard = new OJNChart();
int songid = buffer.getInt();
int signature = buffer.getInt();
if(signature != OJN_SIGNATURE){
Logger.global.log(Level.WARNING, "File [{0}] isn't a OJN file !", file);
return null;
}
float encode_version = buffer.getFloat();
int genre = buffer.getInt();
String str_genre = genre_map[(genre<0||genre>10)?10:genre];
easy.genre = str_genre;
normal.genre = str_genre;
hard.genre = str_genre;
float bpm = buffer.getFloat();
easy.bpm = bpm;
normal.bpm = bpm;
hard.bpm = bpm;
easy.level = buffer.getShort();
normal.level = buffer.getShort();
hard.level = buffer.getShort();
buffer.getShort(); // 0, always
int event_count[] = new int[3];
event_count[0] = buffer.getInt();
event_count[1] = buffer.getInt();
event_count[2] = buffer.getInt();
easy.notes = buffer.getInt();
normal.notes = buffer.getInt();
hard.notes = buffer.getInt();
int measure_count[] = new int[3];
measure_count[0] = buffer.getInt();
measure_count[1] = buffer.getInt();
measure_count[2] = buffer.getInt();
int package_count[] = new int[3];
package_count[0] = buffer.getInt();
package_count[1] = buffer.getInt();
package_count[2] = buffer.getInt();
short old_encode_version = buffer.getShort();
short old_songid = buffer.getShort();
byte old_genre[] = new byte[20];
buffer.get(old_genre);
int bmp_size = buffer.getInt();
int file_version = buffer.getInt();
byte title[] = new byte[64];
buffer.get(title);
String str_title = ByteHelper.toString(title);
easy.title = str_title;
normal.title = str_title;
hard.title = str_title;
byte artist[] = new byte[32];
buffer.get(artist);
String str_artist = ByteHelper.toString(artist);
easy.artist = str_artist;
normal.artist = str_artist;
hard.artist = str_artist;
byte noter[] = new byte[32];
buffer.get(noter);
String str_noter = ByteHelper.toString(noter);
easy.noter = str_noter;
normal.noter = str_noter;
hard.noter = str_noter;
byte ojm_file[] = new byte[32];
buffer.get(ojm_file);
File sample_file = new File(file.getParent(), ByteHelper.toString(ojm_file));
easy.sample_file = sample_file;
normal.sample_file = sample_file;
hard.sample_file = sample_file;
int cover_size = buffer.getInt();
easy.cover_size = cover_size;
normal.cover_size = cover_size;
hard.cover_size = cover_size;
easy.duration = buffer.getInt();
normal.duration = buffer.getInt();
hard.duration = buffer.getInt();
easy.note_offset = buffer.getInt();
normal.note_offset = buffer.getInt();
hard.note_offset = buffer.getInt();
int cover_offset = buffer.getInt();
easy.note_offset_end = normal.note_offset;
normal.note_offset_end = hard.note_offset;
hard.note_offset_end = cover_offset;
easy.cover_offset = cover_offset;
normal.cover_offset = cover_offset;
hard.cover_offset = cover_offset;
easy.source = file;
normal.source = file;
hard.source = file;
ChartList list = new ChartList();
list.add(easy);
list.add(normal);
list.add(hard);
list.source_file = file;
buffer.clear();
try {
f.close();
} catch (IOException ex) {
Logger.global.log(Level.WARNING, "Error closing the file (lol?) {0}", ex);
}
return list;
}
public static EventList parseChart(OJNChart chart)
{
EventList event_list = new EventList();
try{
RandomAccessFile f = new RandomAccessFile(chart.getSource().getAbsolutePath(), "r");
int start = chart.note_offset;
int end = chart.note_offset_end;
ByteBuffer buffer = f.getChannel().map(FileChannel.MapMode.READ_ONLY, start, end - start);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
readNoteBlock(event_list, buffer);
f.close();
}catch(java.io.FileNotFoundException e){
Logger.global.log(Level.WARNING, "File {0} not found !!", chart.getSource().getName());
} catch (IOException e){
Logger.global.log(Level.WARNING, "IO exception on reading OJN file {0}", chart.getSource().getName());
}
return event_list;
}
private static void readNoteBlock(EventList event_list, ByteBuffer buffer) {
while(buffer.hasRemaining())
{
int measure = buffer.getInt();
short channel_number = buffer.getShort();
short events_count = buffer.getShort();
Event.Channel channel;
switch(channel_number)
{
case 0:channel = Event.Channel.TIME_SIGNATURE;break;
case 1:channel = Event.Channel.BPM_CHANGE;break;
case 2:channel = Event.Channel.NOTE_1;break;
case 3:channel = Event.Channel.NOTE_2;break;
case 4:channel = Event.Channel.NOTE_3;break;
case 5:channel = Event.Channel.NOTE_4;break;
case 6:channel = Event.Channel.NOTE_5;break;
case 7:channel = Event.Channel.NOTE_6;break;
case 8:channel = Event.Channel.NOTE_7;break;
default:
channel = Event.Channel.AUTO_PLAY;
}
for(double i=0;i<events_count;i++)
{
double position = i / events_count;
if(channel == Event.Channel.BPM_CHANGE || channel == Event.Channel.TIME_SIGNATURE) // fractional measure or BPM event
{
float v = buffer.getFloat();
if(v == 0)continue;
event_list.add(new Event(channel,measure,position,v,Event.Flag.NONE));
}else{ // note event
short value = buffer.getShort();
int volume_pan = buffer.get();
int type = buffer.get();
if(value == 0)continue; // ignore value=0 events
// MIN 1 ~ 15 MAX, special 0 = MAX
float volume = ((volume_pan >> 4) & 0x0F) / 16f;
if(volume == 0)volume = 1;
// LEFT 1 ~ 8 CENTER 8 ~ 15 RIGHT, special: 0 = 8
float pan = (volume_pan & 0x0F);
if(pan == 0)pan = 8;
pan -= 8;
pan /= 8f; //TODO or maybe 7f? (15-8) / 8 = 7 / 8 = 0.875 and it should be 1, right?
value--; // make zero-based ( zero was the "ignore" value )
// A lot of fixes here are done thanks to keigen shu. He's stealing my protagonism D:
Event.Flag f = Event.Flag.NONE;
if(type%8 > 3)
value += 1000;
type %= 4;
switch(type)
{
case 0:
f = Event.Flag.NONE;
break;
case 1:
//Unused (#W Normal displayed in NoteTool)
break;
case 2:
//fix for autoplay longnotes, convert them to normal notes (it doesn't matter but... still xD)
f = Event.Flag.HOLD;
break;
case 3:
f = Event.Flag.RELEASE;
break;
}
event_list.add(new Event(channel,measure,position,value,f,volume, pan));
}
}
}
Collections.sort(event_list);
}
}