Add in metronome, delay testing, and bpm timing section offsets

This commit is contained in:
Joshua Sigona 2021-10-11 13:14:35 +09:00
parent 3fd0bbb21f
commit 01583d98c6
10 changed files with 208 additions and 31 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,12 @@
package main.java.LLSIG;
public class BeatTiming {
long offset;
int bpm;
boolean active;
BeatTiming(long offset,int bpm) {
this.offset=offset;
this.bpm=bpm;
this.active=true;
}
}

View File

@ -22,6 +22,7 @@ public class Canvas extends JPanel{
} }
public void paintComponent(Graphics g) { public void paintComponent(Graphics g) {
g.setFont(LLSIG.gameFont);
final int MIDDLE_X = this.getWidth()/2; final int MIDDLE_X = this.getWidth()/2;
final int MIDDLE_Y = this.getHeight()-(this.getWidth()/2); final int MIDDLE_Y = this.getHeight()-(this.getWidth()/2);
final int JUDGEMENT_LINE_WIDTH = 64; final int JUDGEMENT_LINE_WIDTH = 64;
@ -36,8 +37,25 @@ public class Canvas extends JPanel{
g.setColor(Color.BLACK); g.setColor(Color.BLACK);
g.fillRect(0,0,this.getWidth(),this.getHeight()); g.fillRect(0,0,this.getWidth(),this.getHeight());
g.setColor(Color.WHITE); g.setColor(Color.WHITE);
g.drawString(Integer.toString(LLSIG.game.frameCount),0,16); g.drawString(Integer.toString(LLSIG.game.musicPlayer.getPlayPosition()),0,32);
g.drawString(Integer.toString(LLSIG.game.musicPlayer.getFrameIndex()),0,64);
if (LLSIG.game.BPM_MEASURE) {
g.drawString("Average BPM: "+LLSIG.approximateBPM(),MIDDLE_X-128,MIDDLE_Y+64);
} else
if (LLSIG.game.METRONOME) {
g.drawString("Offset: "+LLSIG.testOffset,MIDDLE_X-128,MIDDLE_Y+64);
} else {
g.setColor(LLSIG.game.musicPlayer.getPlayPosition()-LLSIG.LAST_PERFECT<500?Color.WHITE:Color.DARK_GRAY);g.drawString("PERFECT: "+LLSIG.PERFECT_COUNT,MIDDLE_X-128,MIDDLE_Y-96);
g.setColor(LLSIG.game.musicPlayer.getPlayPosition()-LLSIG.LAST_EXCELLENT<500?Color.WHITE:Color.DARK_GRAY);g.drawString("EXCELLENT: "+LLSIG.EXCELLENT_COUNT,MIDDLE_X-128,MIDDLE_Y-64);
g.setColor(LLSIG.game.musicPlayer.getPlayPosition()-LLSIG.LAST_GREAT<500?Color.WHITE:Color.DARK_GRAY);g.drawString("GREAT: "+LLSIG.GREAT_COUNT,MIDDLE_X-128,MIDDLE_Y-32);
g.setColor(LLSIG.game.musicPlayer.getPlayPosition()-LLSIG.LAST_EARLY<500?Color.WHITE:Color.DARK_GRAY);g.drawString("EARLY: "+LLSIG.EARLY_COUNT,MIDDLE_X-128,MIDDLE_Y);
g.setColor(LLSIG.game.musicPlayer.getPlayPosition()-LLSIG.LAST_LATE<500?Color.WHITE:Color.DARK_GRAY);g.drawString("LATE: "+LLSIG.LATE_COUNT,MIDDLE_X-128,MIDDLE_Y+32);
g.setColor(LLSIG.game.musicPlayer.getPlayPosition()-LLSIG.LAST_MISS<500?Color.WHITE:Color.DARK_GRAY);g.drawString("MISS: "+LLSIG.MISS_COUNT,MIDDLE_X-128,MIDDLE_Y+64);
}
g.setColor(Color.WHITE);
String comboString = "x"+LLSIG.COMBO+" combo";
Rectangle2D bounds = calculateStringBoundsFont(comboString, g.getFont());
g.drawString(comboString,(int)(MIDDLE_X-bounds.getCenterX()),MIDDLE_Y+164);
for (int i=0;i<9;i++) { for (int i=0;i<9;i++) {
int LANE_X_OFFSET = (i-5)*LANE_SPACING_X+LANE_SPACING_X/2+JUDGEMENT_LINE_WIDTH/2; int LANE_X_OFFSET = (i-5)*LANE_SPACING_X+LANE_SPACING_X/2+JUDGEMENT_LINE_WIDTH/2;
@ -50,7 +68,6 @@ public class Canvas extends JPanel{
int NOTE_X=(int)(MIDDLE_X-Math.cos(Math.toRadians(22.5*i))*NOTE_DISTANCE-NOTE_SIZE/2); int NOTE_X=(int)(MIDDLE_X-Math.cos(Math.toRadians(22.5*i))*NOTE_DISTANCE-NOTE_SIZE/2);
int NOTE_Y=(int)(MIDDLE_Y+Math.sin(Math.toRadians(22.5*i))*NOTE_DISTANCE-NOTE_SIZE/2); int NOTE_Y=(int)(MIDDLE_Y+Math.sin(Math.toRadians(22.5*i))*NOTE_DISTANCE-NOTE_SIZE/2);
g.fillOval(NOTE_X,NOTE_Y,NOTE_SIZE,NOTE_SIZE); g.fillOval(NOTE_X,NOTE_Y,NOTE_SIZE,NOTE_SIZE);
g.setColor(NOTE_COLOR);
Lane lane = LLSIG.game.lanes.get(i); Lane lane = LLSIG.game.lanes.get(i);
if (LLSIG.game.PLAYING) { if (LLSIG.game.PLAYING) {
@ -81,6 +98,7 @@ public class Canvas extends JPanel{
g.drawString(lane.lastRating.name(),(int)(NOTE_X-textBounds.getCenterX()),(int)(NOTE_Y+textBounds.getHeight())); g.drawString(lane.lastRating.name(),(int)(NOTE_X-textBounds.getCenterX()),(int)(NOTE_Y+textBounds.getHeight()));
} }
} }
g.setColor(NOTE_COLOR);
int noteCounter = 0; int noteCounter = 0;
while (lane.noteExists(noteCounter)) { while (lane.noteExists(noteCounter)) {
Note n = lane.getNote(noteCounter); Note n = lane.getNote(noteCounter);

View File

@ -86,12 +86,12 @@ public class JLayerPlayerPausable{
return this.play(0); return this.play(0);
} }
public boolean play(int frameIndexStart) throws JavaLayerException { public boolean play(long frameIndexStart) throws JavaLayerException {
//return this.play(frameIndexStart, -1, 52); //original, mas voltava num ponto anterior ao do pause. 52 Sao os frames perdidos ao dar pause //return this.play(frameIndexStart, -1, 52); //original, mas voltava num ponto anterior ao do pause. 52 Sao os frames perdidos ao dar pause
return this.play(frameIndexStart, -1, lostFrames); return this.play(frameIndexStart, -1, lostFrames);
} }
public boolean play(int frameIndexStart, int frameIndexFinal, int correctionFactorInFrames) throws JavaLayerException{ public boolean play(long frameIndexStart, int frameIndexFinal, int correctionFactorInFrames) throws JavaLayerException{
try { try {
this.bitstream = new Bitstream(this.getAudioInputStream()); this.bitstream = new Bitstream(this.getAudioInputStream());
} }
@ -163,6 +163,10 @@ public class JLayerPlayerPausable{
return shouldContinueReadingFrames; return shouldContinueReadingFrames;
} }
public int getFrameIndex() {
return this.frameIndexCurrent;
}
public boolean resume() throws JavaLayerException{ public boolean resume() throws JavaLayerException{
return this.play(this.frameIndexCurrent); return this.play(this.frameIndexCurrent);

View File

@ -1,5 +1,6 @@
package main.java.LLSIG; package main.java.LLSIG;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.KeyListener; import java.awt.event.KeyListener;
import java.io.File; import java.io.File;
@ -10,6 +11,11 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JFrame; import javax.swing.JFrame;
import main.java.sig.utils.FileUtils; import main.java.sig.utils.FileUtils;
@ -21,15 +27,27 @@ public class LLSIG implements KeyListener{
ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(1); ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(1);
int frameCount; int frameCount;
public static LLSIG game; public static LLSIG game;
int NOTE_SPEED = 850; //The note speed determines how early you see the note. So lowering this number increases the speed. public static Font gameFont = new Font("Century Gothic",Font.BOLD,32);
public static int bpm = 120;
public static long offset = 0;
public static long testOffset = 0;
public static double beatDelay = ((1/((double)bpm/60))*1000);
public static List<Long> beats = new ArrayList<Long>();
int NOTE_SPEED = 750; //The note speed determines how early you see the note. So lowering this number increases the speed.
List<Lane> lanes = new ArrayList<Lane>(); List<Lane> lanes = new ArrayList<Lane>();
List<BeatTiming> timings = new ArrayList<BeatTiming>();
String song = "MiChi - ONE-315959669"; String song = "MiChi - ONE-315959669";
final static Dimension WINDOW_SIZE = new Dimension(1280,1050); final static Dimension WINDOW_SIZE = new Dimension(1280,1050);
public boolean EDITMODE = true; public boolean EDITMODE = false;
public boolean PLAYING = false; //Whether or not a song is loaded and playing. public boolean METRONOME = true;
public boolean BPM_MEASURE = false;
public boolean PLAYING = true; //Whether or not a song is loaded and playing.
public static int beatNumber = 0;
public static boolean[] lanePress = new boolean[9]; //A lane is being requested to being pressed. public static boolean[] lanePress = new boolean[9]; //A lane is being requested to being pressed.
public static boolean[] keyState = new boolean[9]; //Whether or not the key is pressed down. public static boolean[] keyState = new boolean[9]; //Whether or not the key is pressed down.
@ -39,8 +57,42 @@ public class LLSIG implements KeyListener{
final static int GREAT_TIMING_WINDOW = 100; final static int GREAT_TIMING_WINDOW = 100;
final static int BAD_TIMING_WINDOW = 150; final static int BAD_TIMING_WINDOW = 150;
public static int PERFECT_COUNT = 0;
public static int EXCELLENT_COUNT = 0;
public static int GREAT_COUNT = 0;
public static int EARLY_COUNT = 0;
public static int LATE_COUNT = 0;
public static int MISS_COUNT = 0;
public static int LAST_PERFECT = 0;
public static int LAST_EXCELLENT = 0;
public static int LAST_GREAT = 0;
public static int LAST_EARLY = 0;
public static int LAST_LATE = 0;
public static int LAST_MISS = 0;
public static int COMBO = 0;
public static Clip metronome_click1,metronome_click2;
LLSIG(JFrame f) { LLSIG(JFrame f) {
this.window = f; this.window = f;
AudioInputStream audioInputStream;
try {
audioInputStream = AudioSystem.getAudioInputStream(new File("se/metronome_click1.wav").getAbsoluteFile());
try {
metronome_click1 = AudioSystem.getClip();
metronome_click1.open(audioInputStream);
audioInputStream = AudioSystem.getAudioInputStream(new File("se/metronome_click2.wav").getAbsoluteFile());
metronome_click2 = AudioSystem.getClip();
metronome_click2.open(audioInputStream);
} catch (LineUnavailableException e) {
e.printStackTrace();
}
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (int i=0;i<9;i++) { for (int i=0;i<9;i++) {
lanes.add(new Lane(new ArrayList<Note>())); lanes.add(new Lane(new ArrayList<Note>()));
@ -60,10 +112,35 @@ public class LLSIG implements KeyListener{
gameLoop = new Thread() { gameLoop = new Thread() {
public void run() { public void run() {
frameCount++; frameCount++;
if (PLAYING) {
for (BeatTiming bt : timings) {
if (bt.active&&musicPlayer.getPlayPosition()>=bt.offset) {
bt.active=false;
bpm=bt.bpm;
offset=bt.offset;
beatDelay = ((1/((double)bpm/60))*1000);
System.out.println("BPM is "+bpm+". Delay is "+beatDelay);
}
}
if (METRONOME) {
if (beatNumber*beatDelay+offset<musicPlayer.getPlayPosition()) {
beatNumber++;
if (beatNumber%4==0) {
metronome_click1.setFramePosition(0);
metronome_click1.start();
} else {
metronome_click2.setFramePosition(0);
metronome_click2.start();
}
}
}
}
for (int i=0;i<9;i++) { for (int i=0;i<9;i++) {
Lane l =lanes.get(i); Lane l =lanes.get(i);
l.markMissedNotes(); l.markMissedNotes();
l.clearOutInactiveNotes(); if (!EDITMODE) {
l.clearOutInactiveNotes();
}
} }
window.repaint(); window.repaint();
} }
@ -74,22 +151,34 @@ public class LLSIG implements KeyListener{
} }
private void LoadSongData(String song,List<Lane> lanes) { private void LoadSongData(String song,List<Lane> lanes) {
lanes.clear();
for (int i=0;i<9;i++) {
lanes.add(new Lane(new ArrayList<Note>()));
}
timings.clear();
try { try {
String[] data = FileUtils.readFromFile("music/"+song+".sig"); String[] data = FileUtils.readFromFile("music/"+song+".sig");
for (String line : data) { for (String line : data) {
String[] split = line.split(Pattern.quote(",")); String[] split = line.split(Pattern.quote(","));
int lane = Integer.parseInt(split[0]); if (split[0].equals("B")) {
NoteType noteType = NoteType.valueOf(split[1]); offset=Integer.parseInt(split[1]);
int offset = Integer.parseInt(split[2]); bpm=Integer.parseInt(split[2]);
int offset2 = -1; beatDelay = ((1/((double)bpm/60))*1000);
while (lanes.size()<lane) { timings.add(new BeatTiming(offset,bpm));
lanes.add(new Lane(new ArrayList<Note>()));
}
if (noteType==NoteType.HOLD) {
offset2 = Integer.parseInt(split[2]);
lanes.get(lane-1).addNote(new Note(noteType,offset,offset2));
} else { } else {
lanes.get(lane-1).addNote(new Note(noteType,offset)); int lane = Integer.parseInt(split[0]);
NoteType noteType = NoteType.valueOf(split[1]);
int offset = (int)Math.round(Integer.parseInt(split[2])*beatDelay+LLSIG.offset);
int offset2 = -1;
while (lanes.size()<lane) {
lanes.add(new Lane(new ArrayList<Note>()));
}
if (noteType==NoteType.HOLD) {
offset2 = (int)Math.round(Integer.parseInt(split[2])*beatDelay+LLSIG.offset);
lanes.get(lane-1).addNote(new Note(noteType,offset,offset2));
} else {
lanes.get(lane-1).addNote(new Note(noteType,offset));
}
} }
} }
} catch (IOException e) { } catch (IOException e) {
@ -102,6 +191,9 @@ public class LLSIG implements KeyListener{
for (int lane=0;lane<lanes.size();lane++) { for (int lane=0;lane<lanes.size();lane++) {
Lane l = lanes.get(lane); Lane l = lanes.get(lane);
int noteCount=0; int noteCount=0;
for (Note n : l.noteChart) {
n.active=true;
}
while (l.noteExists(noteCount)) { while (l.noteExists(noteCount)) {
Note n = l.getNote(noteCount++); Note n = l.getNote(noteCount++);
data.add(new StringBuilder().append(lane+1).append(",") data.add(new StringBuilder().append(lane+1).append(",")
@ -120,6 +212,19 @@ public class LLSIG implements KeyListener{
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game = new LLSIG(f); game = new LLSIG(f);
} }
public static double approximateBPM() {
long totalDiff = 0;
if (beats.size()>=2) {
for (int i=1;i<beats.size();i++) {
totalDiff+=beats.get(i)-beats.get(i-1);
}
long averageDiff = totalDiff/(beats.size()-1);
return (1/((double)averageDiff/1000000000l)*60);
} else {
return 0;
}
}
@Override @Override
public void keyTyped(KeyEvent e) { public void keyTyped(KeyEvent e) {
@ -130,7 +235,18 @@ public class LLSIG implements KeyListener{
public void keyPressed(KeyEvent e) { public void keyPressed(KeyEvent e) {
int lane = -1; int lane = -1;
switch (e.getKeyCode()) { switch (e.getKeyCode()) {
case KeyEvent.VK_A:{lane=0;}break; case KeyEvent.VK_A:{
if (BPM_MEASURE) {
beats.add(System.nanoTime());
}
lane=0;
}break;
case KeyEvent.VK_BACK_SLASH:{
if (METRONOME) {
testOffset=musicPlayer.getPlayPosition();
beatNumber=0;
}
}break;
case KeyEvent.VK_S:{lane=1;}break; case KeyEvent.VK_S:{lane=1;}break;
case KeyEvent.VK_D:{lane=2;}break; case KeyEvent.VK_D:{lane=2;}break;
case KeyEvent.VK_F:{lane=3;}break; case KeyEvent.VK_F:{lane=3;}break;
@ -152,10 +268,21 @@ public class LLSIG implements KeyListener{
Note n = l.getNote(); Note n = l.getNote();
int diff = n.getStartFrame()-LLSIG.game.musicPlayer.getPlayPosition(); int diff = n.getStartFrame()-LLSIG.game.musicPlayer.getPlayPosition();
if (diff<=BAD_TIMING_WINDOW) { if (diff<=BAD_TIMING_WINDOW) {
if (Math.abs(diff)<=PERFECT_TIMING_WINDOW) {l.lastRating=TimingRating.PERFECT;} else if (Math.abs(diff)<=PERFECT_TIMING_WINDOW) {l.lastRating=TimingRating.PERFECT;COMBO++;PERFECT_COUNT++;LAST_PERFECT=LLSIG.game.musicPlayer.getPlayPosition();} else
if (Math.abs(diff)<=EXCELLENT_TIMING_WINDOW) {l.lastRating=TimingRating.EXCELLENT;} else if (Math.abs(diff)<=EXCELLENT_TIMING_WINDOW) {l.lastRating=TimingRating.EXCELLENT;COMBO++;EXCELLENT_COUNT++;LAST_EXCELLENT=LLSIG.game.musicPlayer.getPlayPosition();} else
if (Math.abs(diff)<=GREAT_TIMING_WINDOW) {l.lastRating=TimingRating.GREAT;} else if (Math.abs(diff)<=GREAT_TIMING_WINDOW) {l.lastRating=TimingRating.GREAT;COMBO++;GREAT_COUNT++;LAST_GREAT=LLSIG.game.musicPlayer.getPlayPosition();} else
if (Math.abs(diff)<=BAD_TIMING_WINDOW) {l.lastRating=Math.signum(diff)>0?TimingRating.EARLY:TimingRating.LATE;} if (Math.abs(diff)<=BAD_TIMING_WINDOW) {
if (Math.signum(diff)>0) {
l.lastRating=TimingRating.EARLY;
EARLY_COUNT++;
LAST_EARLY=LLSIG.game.musicPlayer.getPlayPosition();
} else {
l.lastRating=TimingRating.LATE;
LATE_COUNT++;
LAST_LATE=LLSIG.game.musicPlayer.getPlayPosition();
}
COMBO=0;
}
l.lastNote=LLSIG.game.musicPlayer.getPlayPosition(); l.lastNote=LLSIG.game.musicPlayer.getPlayPosition();
n.active=false; n.active=false;
} }

View File

@ -25,7 +25,7 @@ public class Lane{
public Note getNote(int noteOffset) throws IndexOutOfBoundsException { public Note getNote(int noteOffset) throws IndexOutOfBoundsException {
for (int i=noteOffset;i<noteChart.size();i++) for (int i=noteOffset;i<noteChart.size();i++)
{ {
Note n = getNote(i); Note n = noteChart.get(i);
if (n.active) {return n;} if (n.active) {return n;}
} }
return null; return null;
@ -68,6 +68,9 @@ public class Lane{
note.active=false; note.active=false;
lastRating = TimingRating.MISS; lastRating = TimingRating.MISS;
lastNote = LLSIG.game.musicPlayer.getPlayPosition(); lastNote = LLSIG.game.musicPlayer.getPlayPosition();
LLSIG.COMBO=0;
LLSIG.MISS_COUNT++;
LLSIG.LAST_MISS=LLSIG.game.musicPlayer.getPlayPosition();
} }
}); });
} }

View File

@ -6,16 +6,13 @@ public class Player {
String song; String song;
public Player(String song) { public Player(String song) {
this.song=song; this.song=song;
try {
jlpp = new JLayerPlayerPausable(song);
} catch (JavaLayerException e) {
e.printStackTrace();
}
} }
public void play() { public void play() {
new Thread() { new Thread() {
public void run() { public void run() {
try { try {
if (jlpp!=null) {jlpp.close();}
jlpp = new JLayerPlayerPausable(song);
jlpp.play(); jlpp.play();
} catch (JavaLayerException e) { } catch (JavaLayerException e) {
e.printStackTrace(); e.printStackTrace();
@ -23,6 +20,19 @@ public class Player {
} }
}.start(); }.start();
} }
public void play(long frame) {
new Thread() {
public void run() {
try {
if (jlpp!=null) {jlpp.close();}
jlpp = new JLayerPlayerPausable(song);
jlpp.play(frame);
} catch (JavaLayerException e) {
e.printStackTrace();
}
}
}.start();
}
public void pause() { public void pause() {
jlpp.pause(); jlpp.pause();
} }
@ -46,4 +56,7 @@ public class Player {
public int getPlayPosition() { public int getPlayPosition() {
return jlpp.getPosition(); return jlpp.getPosition();
} }
public int getFrameIndex() {
return jlpp.getFrameIndex();
}
} }