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) {
g.setFont(LLSIG.gameFont);
final int MIDDLE_X = this.getWidth()/2;
final int MIDDLE_Y = this.getHeight()-(this.getWidth()/2);
final int JUDGEMENT_LINE_WIDTH = 64;
@ -36,8 +37,25 @@ public class Canvas extends JPanel{
g.setColor(Color.BLACK);
g.fillRect(0,0,this.getWidth(),this.getHeight());
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++) {
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_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.setColor(NOTE_COLOR);
Lane lane = LLSIG.game.lanes.get(i);
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.setColor(NOTE_COLOR);
int noteCounter = 0;
while (lane.noteExists(noteCounter)) {
Note n = lane.getNote(noteCounter);

View File

@ -86,12 +86,12 @@ public class JLayerPlayerPausable{
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, lostFrames);
}
public boolean play(int frameIndexStart, int frameIndexFinal, int correctionFactorInFrames) throws JavaLayerException{
public boolean play(long frameIndexStart, int frameIndexFinal, int correctionFactorInFrames) throws JavaLayerException{
try {
this.bitstream = new Bitstream(this.getAudioInputStream());
}
@ -163,6 +163,10 @@ public class JLayerPlayerPausable{
return shouldContinueReadingFrames;
}
public int getFrameIndex() {
return this.frameIndexCurrent;
}
public boolean resume() throws JavaLayerException{
return this.play(this.frameIndexCurrent);

View File

@ -1,5 +1,6 @@
package main.java.LLSIG;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
@ -10,6 +11,11 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
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 main.java.sig.utils.FileUtils;
@ -21,15 +27,27 @@ public class LLSIG implements KeyListener{
ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(1);
int frameCount;
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<BeatTiming> timings = new ArrayList<BeatTiming>();
String song = "MiChi - ONE-315959669";
final static Dimension WINDOW_SIZE = new Dimension(1280,1050);
public boolean EDITMODE = true;
public boolean PLAYING = false; //Whether or not a song is loaded and playing.
public boolean EDITMODE = false;
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[] 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 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) {
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++) {
lanes.add(new Lane(new ArrayList<Note>()));
@ -60,10 +112,35 @@ public class LLSIG implements KeyListener{
gameLoop = new Thread() {
public void run() {
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++) {
Lane l =lanes.get(i);
l.markMissedNotes();
l.clearOutInactiveNotes();
if (!EDITMODE) {
l.clearOutInactiveNotes();
}
}
window.repaint();
}
@ -74,22 +151,34 @@ public class LLSIG implements KeyListener{
}
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 {
String[] data = FileUtils.readFromFile("music/"+song+".sig");
for (String line : data) {
String[] split = line.split(Pattern.quote(","));
int lane = Integer.parseInt(split[0]);
NoteType noteType = NoteType.valueOf(split[1]);
int offset = Integer.parseInt(split[2]);
int offset2 = -1;
while (lanes.size()<lane) {
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));
if (split[0].equals("B")) {
offset=Integer.parseInt(split[1]);
bpm=Integer.parseInt(split[2]);
beatDelay = ((1/((double)bpm/60))*1000);
timings.add(new BeatTiming(offset,bpm));
} 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) {
@ -102,6 +191,9 @@ public class LLSIG implements KeyListener{
for (int lane=0;lane<lanes.size();lane++) {
Lane l = lanes.get(lane);
int noteCount=0;
for (Note n : l.noteChart) {
n.active=true;
}
while (l.noteExists(noteCount)) {
Note n = l.getNote(noteCount++);
data.add(new StringBuilder().append(lane+1).append(",")
@ -120,6 +212,19 @@ public class LLSIG implements KeyListener{
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
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
public void keyTyped(KeyEvent e) {
@ -130,7 +235,18 @@ public class LLSIG implements KeyListener{
public void keyPressed(KeyEvent e) {
int lane = -1;
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_D:{lane=2;}break;
case KeyEvent.VK_F:{lane=3;}break;
@ -152,10 +268,21 @@ public class LLSIG implements KeyListener{
Note n = l.getNote();
int diff = n.getStartFrame()-LLSIG.game.musicPlayer.getPlayPosition();
if (diff<=BAD_TIMING_WINDOW) {
if (Math.abs(diff)<=PERFECT_TIMING_WINDOW) {l.lastRating=TimingRating.PERFECT;} else
if (Math.abs(diff)<=EXCELLENT_TIMING_WINDOW) {l.lastRating=TimingRating.EXCELLENT;} else
if (Math.abs(diff)<=GREAT_TIMING_WINDOW) {l.lastRating=TimingRating.GREAT;} else
if (Math.abs(diff)<=BAD_TIMING_WINDOW) {l.lastRating=Math.signum(diff)>0?TimingRating.EARLY:TimingRating.LATE;}
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;COMBO++;EXCELLENT_COUNT++;LAST_EXCELLENT=LLSIG.game.musicPlayer.getPlayPosition();} 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) {
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();
n.active=false;
}

View File

@ -25,7 +25,7 @@ public class Lane{
public Note getNote(int noteOffset) throws IndexOutOfBoundsException {
for (int i=noteOffset;i<noteChart.size();i++)
{
Note n = getNote(i);
Note n = noteChart.get(i);
if (n.active) {return n;}
}
return null;
@ -68,6 +68,9 @@ public class Lane{
note.active=false;
lastRating = TimingRating.MISS;
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;
public Player(String song) {
this.song=song;
try {
jlpp = new JLayerPlayerPausable(song);
} catch (JavaLayerException e) {
e.printStackTrace();
}
}
public void play() {
new Thread() {
public void run() {
try {
if (jlpp!=null) {jlpp.close();}
jlpp = new JLayerPlayerPausable(song);
jlpp.play();
} catch (JavaLayerException e) {
e.printStackTrace();
@ -23,6 +20,19 @@ public class Player {
}
}.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() {
jlpp.pause();
}
@ -46,4 +56,7 @@ public class Player {
public int getPlayPosition() {
return jlpp.getPosition();
}
public int getFrameIndex() {
return jlpp.getFrameIndex();
}
}