Completed music feature, added score selection and software-defined repeat mode for songs

This commit is contained in:
Brendan Robert 2021-04-26 09:24:21 -05:00
parent f47713b4e0
commit 2278344278
7 changed files with 173 additions and 62 deletions

View File

@ -10,26 +10,12 @@ import jace.core.Card;
import jace.core.Computer;
import jace.core.Motherboard;
import jace.core.Utility;
import jace.lawless.LawlessComputer;
import jace.lawless.LawlessHacks;
import jace.library.MediaCache;
import jace.library.MediaConsumer;
import jace.library.MediaConsumerParent;
import jace.library.MediaEntry;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.FadeTransition;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
@ -44,6 +30,7 @@ import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.effect.DropShadow;
@ -52,18 +39,21 @@ import javafx.scene.input.DragEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.util.StringConverter;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author blurry
@ -97,6 +87,9 @@ public class JaceUIController {
@FXML
private Button menuButton;
@FXML
private ComboBox musicSelection;
Computer computer;
private final BooleanProperty aspectRatioCorrectionEnabled = new SimpleBooleanProperty(false);
@ -166,6 +159,12 @@ public class JaceUIController {
}
private void hideControlOverlay(MouseEvent evt) {
if (evt == null || evt.getSource() != null && (
evt.getSource() == musicSelection ||
(evt.getSource() == rootPane && musicSelection.isFocused())
)) {
return;
}
if (menuButtonPane.isVisible()) {
FadeTransition ft1 = new FadeTransition(Duration.millis(500), menuButtonPane);
ft1.setFromValue(1.0);
@ -237,6 +236,9 @@ public class JaceUIController {
// Kind of redundant but make sure speed is properly set as if the user did it
setSpeed(Emulator.logic.speedSetting);
});
musicSelection.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
((LawlessHacks) ((LawlessComputer) computer).activeCheatEngine).changeMusicScore(String.valueOf(newValue));
});
}
private void connectButtons(Node n) {

View File

@ -34,7 +34,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*
* @author blurry
*/
public class LawlessLegends extends Application {

View File

@ -20,25 +20,16 @@ package jace.apple2e;
import jace.LawlessLegends;
import jace.config.ConfigurableField;
import jace.core.Computer;
import jace.core.Device;
import jace.core.Motherboard;
import jace.core.RAMEvent;
import jace.core.RAMListener;
import jace.core.SoundGeneratorDevice;
import jace.core.SoundMixer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import jace.core.*;
import javafx.stage.FileChooser;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import java.io.*;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.stage.FileChooser;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
/**
* Apple // Speaker Emulation Created on May 9, 2007, 9:55 PM
@ -205,7 +196,9 @@ public class Speaker extends SoundGeneratorDevice {
bufferPos = 0;
}
secondaryBuffer = buffer;
sdl.write(buffer, 0, len);
if (sdl != null && buffer != null) {
sdl.write(buffer, 0, len);
}
}
/**

View File

@ -8,8 +8,16 @@ import javafx.beans.property.DoubleProperty;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Hacks that affect lawless legends gameplay
@ -23,6 +31,8 @@ public class LawlessHacks extends Cheats {
public LawlessHacks(Computer computer) {
super(computer);
readScores();
currentScore = SCORE_ORCHESTRAL;
}
@Override
@ -64,13 +74,15 @@ public class LawlessHacks extends Cheats {
}
int currentSong;
boolean repeatSong = false;
Thread playbackEffect;
MediaPlayer currentSongPlayer;
MediaPlayer currentSfxPlayer;
private void playSound(int soundNumber) {
boolean isMusic = soundNumber >= 0;
int track = soundNumber & 0x07f;
int track = soundNumber & 0x03f;
repeatSong = (soundNumber & 0x040) > 0;
if (track == 0) {
if (isMusic) {
// System.out.println("Stop music");
@ -88,8 +100,20 @@ public class LawlessHacks extends Cheats {
}
}
private Media getAudioTrack(String file) {
String pathStr = "jace/data/sound/" + file;
private Media getAudioTrack(int number) {
Map<Integer, String> score = scores.get(currentScore);
if (score == null) {
return null;
}
String filename = score.get(number);
if (filename == null) {
score = scores.get("common");
if (score == null || !score.containsKey(number)) {
return null;
}
filename = score.get(number);
}
String pathStr = "jace/data/sound/" + filename;
// System.out.println("looking in "+pathStr);
URL path = getClass().getClassLoader().getResource(pathStr);
if (path == null) {
@ -150,15 +174,15 @@ public class LawlessHacks extends Cheats {
int FADE_SPEED = 100; // 100ms per 5%, or 2 second duration
private void startNewSong(int track) {
if (track != currentSong || currentSongPlayer == null) {
if (track != currentSong || currentSongPlayer == null || isMusicEnabled()) {
// If the same song is already playing don't restart it
Media song = getAudioTrack("BGM-" + track + ".mp3");
Media song = getAudioTrack(track);
if (song == null) {
System.out.println("Unable to start song " + track + "; File not found");
return;
}
currentSongPlayer = new MediaPlayer(song);
currentSongPlayer.setCycleCount(MediaPlayer.INDEFINITE);
currentSongPlayer.setCycleCount(repeatSong ? MediaPlayer.INDEFINITE : 1);
currentSongPlayer.setVolume(0.0);
currentSongPlayer.play();
currentSong = track;
@ -185,12 +209,16 @@ public class LawlessHacks extends Cheats {
private void stopMusic() {
stopSongEffect();
fadeOutSong(null);
fadeOutSong(()->{
if (!repeatSong) {
currentSong = 0;
}
});
}
private void playSfx(int track) {
new Thread(() -> {
Media sfx = getAudioTrack("SFX-" + track + ".mp3");
Media sfx = getAudioTrack(track + 128);
if (sfx == null) {
System.out.println("Unable to start SFX " + track + "; File not found");
return;
@ -208,4 +236,62 @@ public class LawlessHacks extends Cheats {
}
}
public static final String SCORE_NONE = "none";
public static final String SCORE_COMMON = "common";
public static final String SCORE_ORCHESTRAL = "orchestral";
public static final String SCORE_CHIPTUNE = "chiptune";
private String currentScore = SCORE_COMMON;
public void changeMusicScore(String score) {
if (currentScore.equalsIgnoreCase(score)) {
return;
}
boolean wasStoppedPreviously = !isMusicEnabled();
currentScore = score.toLowerCase(Locale.ROOT);
if (currentScore.equalsIgnoreCase(SCORE_NONE)) {
stopMusic();
} else if ((currentSongPlayer != null || wasStoppedPreviously) && currentSong > 0) {
int currentSongTemp = currentSong;
currentSong = Integer.MAX_VALUE;
startNewSong(currentSongTemp);
}
}
public boolean isMusicEnabled() {
return currentScore != null && !currentScore.equalsIgnoreCase(SCORE_NONE);
}
Pattern COMMENT = Pattern.compile("\\s*[-#;']+.*");
Pattern LABEL = Pattern.compile("[A-Za-z\\s\\-_]+");
Pattern ENTRY = Pattern.compile("([0-9]+)\\s+(.*)");
private Map<String, Map<Integer, String>> scores = new HashMap<>();
private void readScores() {
InputStream data = getClass().getClassLoader().getResourceAsStream("jace/data/sound/scores.txt");
readScores(data);
}
private void readScores(InputStream data) {
BufferedReader reader = new BufferedReader(new InputStreamReader(data));
reader.lines().forEach(line -> {
if (COMMENT.matcher(line).matches() || line.trim().isEmpty()) {
// System.out.println("Ignoring: "+line);
return;
} else if (LABEL.matcher(line).matches()) {
currentScore = line.toLowerCase(Locale.ROOT);
scores.put(currentScore, new HashMap<>());
// System.out.println("Score: "+ currentScore);
} else {
Matcher m = ENTRY.matcher(line);
if (m.matches()) {
int num = Integer.parseInt(m.group(1));
String file = m.group(2);
scores.get(currentScore).put(num, file);
// System.out.println("Score: " + currentScore + "; Song: " + num + "; " + file);
} else {
// System.out.println("Couldn't parse: " + line);
}
}
});
}
}

View File

@ -1,17 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.collections.FXCollections?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.TilePane?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import java.lang.String?>
<AnchorPane id="AnchorPane" fx:id="rootPane" prefHeight="384.0" prefWidth="560.0" style="-fx-background-color: black;" stylesheets="@../styles/style.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jace.JaceUIController">
<children>
<StackPane fx:id="stackPane" prefHeight="384.0" prefWidth="560.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
@ -83,8 +77,22 @@
</Button>
</children>
</TilePane>
<!-- <TilePane alignment="TOP_RIGHT" hgap="5.0" vgap="5.0" HBox.hgrow="ALWAYS">
<TilePane alignment="TOP_RIGHT" hgap="5.0" vgap="5.0" HBox.hgrow="ALWAYS">
<children>
<Label styleClass="musicLabel">Music:</Label>
<ComboBox fx:id="musicSelection">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="Orchestral" />
<String fx:value="Chiptune" />
<String fx:value="None" />
</FXCollections>
</items>
<value>
<String fx:value="Orchestral"/>
</value>
</ComboBox>
<!--
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="IDE">
<graphic>
<ImageView>
@ -112,8 +120,9 @@
</ImageView>
</graphic>
</Button>
-->
</children>
</TilePane>-->
</TilePane>
</children>
</HBox>
</top>

View File

@ -0,0 +1,16 @@
---- Scores file defines all the music and SFX
---- Note: Anything that is shared for all scores is under the "common" section
---- Lines that start with dashes, hash marks, single quotes are treated as comments
Common
---- Orchestral version uses sampled synthesis
Orchestral
1 BGM-1.mp3
2 someotherfile.mp3
--- Sound effects start at number 129
129 bang.mp3
130 boom.mp3
---- Chiptune verison uses the blips and bleeps that are the voice of a generation
Chiptune
1 BGM-2.mp3

View File

@ -26,11 +26,16 @@
-fx-background-radius: 10px;
}
.menuButton, .uiActionButton, .uiSpeedSlider ImageView, .uiSpeedSlider Slider, .uiSpeedSlider AnchorPane {
.menuButton, .uiActionButton, .uiSpeedSlider ImageView, .uiSpeedSlider Slider, .uiSpeedSlider AnchorPane, .musicLabel {
-fx-background-color: rgba(0, 0, 0, 0.75);
-fx-text-fill: #a0FFa0
}
.musicLabel {
-fx-text-alignment: right;
-fx-font-size:18pt;
}
.uiActionButton ImageView, .uiSpeedSlider ImageView {
-fx-effect: dropshadow(gaussian , rgba(64,255,64,0.25) , 2,1.0,0,0);
}