Update MusicPlayer.java
This commit is contained in:
parent
d37436289a
commit
3556e67c71
1 changed files with 288 additions and 53 deletions
|
@ -1,82 +1,317 @@
|
|||
package jfxlabproj.musicplayer;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.PixelReader;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javax.sound.sampled.*;
|
||||
|
||||
public class MusicPlayer {
|
||||
|
||||
private static final int BASE_NOTE = 262; // C4 note
|
||||
private ScheduledExecutorService executor;
|
||||
private volatile boolean isPlaying = false;
|
||||
private static final int SAMPLE_RATE = 44100;
|
||||
private static final int SAMPLE_SIZE = 16;
|
||||
private static final int CHANNELS = 2;
|
||||
private static final boolean SIGNED = true;
|
||||
private static final boolean BIG_ENDIAN = true;
|
||||
private static final int VISUALIZER_WIDTH = 200;
|
||||
private static final int VISUALIZER_HEIGHT = 80;
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
private static final int PIXEL_SKIP = 100; // Skip factor for pixel processing
|
||||
|
||||
public void playImageAsMusic(Image image) {
|
||||
if (image == null) return;
|
||||
if (isPlaying) return; // Prevent starting multiple playbacks simultaneously
|
||||
private SourceDataLine line;
|
||||
private boolean isPlaying = false;
|
||||
private Thread playThread;
|
||||
private byte[] audioBuffer;
|
||||
|
||||
PixelReader reader = image.getPixelReader();
|
||||
int width = (int) image.getWidth();
|
||||
int height = (int) image.getHeight();
|
||||
private TextArea infoBox;
|
||||
private Stage musicInfoStage;
|
||||
private double progress = 0;
|
||||
private Canvas visualizer;
|
||||
private GraphicsContext gc;
|
||||
private double[] audioData = new double[100];
|
||||
private int audioDataIndex = 0;
|
||||
private AnimationTimer visualizerTimer;
|
||||
private double lastFrequency = 0;
|
||||
|
||||
executor = Executors.newScheduledThreadPool(1);
|
||||
isPlaying = true;
|
||||
executor.scheduleAtFixedRate(
|
||||
() -> {
|
||||
Random random = new Random();
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (!isPlaying) return; // Stop processing if paused
|
||||
public MusicPlayer() {
|
||||
initializeUIComponents();
|
||||
setupAudioLine();
|
||||
audioBuffer = new byte[BUFFER_SIZE];
|
||||
}
|
||||
|
||||
Color color = reader.getColor(x, y);
|
||||
double brightness = color.getBrightness();
|
||||
int note = BASE_NOTE + (int) (brightness * 300); // Vary note based on brightness, reduced range for smoother sound
|
||||
int duration = 150 + random.nextInt(150); // Randomize duration between 150ms and 300ms
|
||||
playTone(note, duration, 0.2f); // Reduce volume
|
||||
}
|
||||
}
|
||||
},
|
||||
0,
|
||||
300,
|
||||
TimeUnit.MILLISECONDS
|
||||
private void initializeUIComponents() {
|
||||
visualizer = new Canvas(VISUALIZER_WIDTH, VISUALIZER_HEIGHT);
|
||||
gc = visualizer.getGraphicsContext2D();
|
||||
gc.setStroke(Color.web("#007AFF"));
|
||||
gc.setLineWidth(2);
|
||||
|
||||
infoBox = new TextArea();
|
||||
infoBox.setEditable(false);
|
||||
infoBox.setWrapText(true);
|
||||
infoBox.setPrefRowCount(6);
|
||||
infoBox.setPrefColumnCount(30);
|
||||
infoBox.setStyle(
|
||||
"-fx-font-family: 'SF Pro Text'; -fx-font-size: 14px;"
|
||||
);
|
||||
infoBox.setText(
|
||||
"Image to Music Conversion:\n\n" +
|
||||
"1. RGB to frequency mapping\n" +
|
||||
"2. Brightness controls volume\n" +
|
||||
"3. Frequency ranges:\n" +
|
||||
" R: 20-400Hz | G: 400-4000Hz | B: 4000-20000Hz\n\n" +
|
||||
"Currently processing: Initializing..."
|
||||
);
|
||||
|
||||
VBox infoContainer = new VBox(10);
|
||||
infoContainer.setStyle(
|
||||
"-fx-padding: 10px; -fx-background-color: white;" +
|
||||
"-fx-background-radius: 10px; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.1), 10, 0, 0, 5);"
|
||||
);
|
||||
infoContainer.getChildren().addAll(visualizer, infoBox);
|
||||
|
||||
musicInfoStage = new Stage(StageStyle.DECORATED);
|
||||
musicInfoStage.setTitle("Music Generation");
|
||||
musicInfoStage.setScene(new Scene(infoContainer));
|
||||
musicInfoStage.setAlwaysOnTop(true);
|
||||
musicInfoStage.setResizable(false);
|
||||
|
||||
visualizerTimer = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
drawVisualizer();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void pauseMusic() {
|
||||
if (executor != null) {
|
||||
isPlaying = false;
|
||||
executor.shutdown();
|
||||
private void drawVisualizer() {
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(0, 0, VISUALIZER_WIDTH, VISUALIZER_HEIGHT);
|
||||
|
||||
gc.beginPath();
|
||||
gc.moveTo(0, VISUALIZER_HEIGHT / 2);
|
||||
|
||||
for (int i = 0; i < audioData.length; i++) {
|
||||
double x = i * (VISUALIZER_WIDTH / (audioData.length - 1));
|
||||
double y =
|
||||
VISUALIZER_HEIGHT / 2 + (audioData[i] * VISUALIZER_HEIGHT) / 3;
|
||||
gc.lineTo(x, y);
|
||||
}
|
||||
|
||||
gc.stroke();
|
||||
}
|
||||
|
||||
private void playTone(int hz, int duration, float volume) {
|
||||
private void setupAudioLine() {
|
||||
try {
|
||||
byte[] buf = new byte[1];
|
||||
AudioFormat audioFormat = new AudioFormat(44100, 8, 1, true, false);
|
||||
SourceDataLine sdl = AudioSystem.getSourceDataLine(audioFormat);
|
||||
sdl.open(audioFormat);
|
||||
sdl.start();
|
||||
|
||||
FloatControl volumeControl = (FloatControl) sdl.getControl(
|
||||
FloatControl.Type.MASTER_GAIN
|
||||
AudioFormat format = new AudioFormat(
|
||||
SAMPLE_RATE,
|
||||
SAMPLE_SIZE,
|
||||
CHANNELS,
|
||||
SIGNED,
|
||||
BIG_ENDIAN
|
||||
);
|
||||
DataLine.Info info = new DataLine.Info(
|
||||
SourceDataLine.class,
|
||||
format
|
||||
);
|
||||
volumeControl.setValue(20f * (float) Math.log10(volume)); // Set volume level
|
||||
|
||||
for (int i = 0; i < (duration * 44100) / 1000; i++) {
|
||||
double angle = (i / (44100.0 / hz)) * 2.0 * Math.PI;
|
||||
buf[0] = (byte) (Math.sin(angle) * 100.0); // Reduce amplitude for a softer sound
|
||||
sdl.write(buf, 0, 1);
|
||||
if (!AudioSystem.isLineSupported(info)) {
|
||||
throw new LineUnavailableException("Audio line not supported");
|
||||
}
|
||||
|
||||
sdl.drain();
|
||||
sdl.stop();
|
||||
sdl.close();
|
||||
line = (SourceDataLine) AudioSystem.getLine(info);
|
||||
line.open(format, BUFFER_SIZE);
|
||||
} catch (LineUnavailableException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void playImageAsMusic(Image image) {
|
||||
if (isPlaying) return;
|
||||
|
||||
musicInfoStage.show();
|
||||
isPlaying = true;
|
||||
line.start();
|
||||
visualizerTimer.start();
|
||||
|
||||
playThread = new Thread(() -> {
|
||||
try {
|
||||
PixelReader pixelReader = image.getPixelReader();
|
||||
int width = (int) image.getWidth();
|
||||
int height = (int) image.getHeight();
|
||||
int bufferIndex = 0;
|
||||
|
||||
for (int y = 0; y < height && isPlaying; y += PIXEL_SKIP) {
|
||||
for (int x = 0; x < width && isPlaying; x += PIXEL_SKIP) {
|
||||
Color color = pixelReader.getColor(x, y);
|
||||
|
||||
// Map colors to pentatonic scale frequencies
|
||||
double baseFreq = 220.0; // A3
|
||||
int[] pentatonic = { 0, 2, 4, 7, 9, 12 }; // Pentatonic scale intervals
|
||||
int noteIndex = (int) (color.getRed() * 5);
|
||||
double redFreq =
|
||||
baseFreq *
|
||||
Math.pow(2, pentatonic[noteIndex] / 12.0);
|
||||
|
||||
noteIndex = (int) (color.getGreen() * 5);
|
||||
double greenFreq =
|
||||
(baseFreq * 2) *
|
||||
Math.pow(2, pentatonic[noteIndex] / 12.0);
|
||||
|
||||
noteIndex = (int) (color.getBlue() * 5);
|
||||
double blueFreq =
|
||||
(baseFreq * 4) *
|
||||
Math.pow(2, pentatonic[noteIndex] / 12.0);
|
||||
|
||||
double targetFreq =
|
||||
(redFreq + greenFreq + blueFreq) / 3.0;
|
||||
|
||||
// Slower, smoother transition
|
||||
lastFrequency =
|
||||
lastFrequency + (targetFreq - lastFrequency) * 0.1;
|
||||
double amplitude = color.getBrightness() * 0.3; // Reduced volume further
|
||||
|
||||
audioData[audioDataIndex] = amplitude;
|
||||
audioDataIndex =
|
||||
(audioDataIndex + 1) % audioData.length;
|
||||
|
||||
byte[] soundLeft = generateTone(
|
||||
lastFrequency,
|
||||
50,
|
||||
amplitude
|
||||
);
|
||||
byte[] soundRight = generateTone(
|
||||
lastFrequency * 1.003,
|
||||
50,
|
||||
amplitude * 0.95
|
||||
);
|
||||
byte[] stereoSound = mergeStereoChannels(
|
||||
soundLeft,
|
||||
soundRight
|
||||
);
|
||||
|
||||
for (byte b : stereoSound) {
|
||||
audioBuffer[bufferIndex++] = b;
|
||||
if (bufferIndex == BUFFER_SIZE) {
|
||||
line.write(audioBuffer, 0, BUFFER_SIZE);
|
||||
bufferIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
final double currentProgress =
|
||||
(y * width + x) / (double) (width * height);
|
||||
final int currentY = y;
|
||||
Platform.runLater(() -> {
|
||||
progress = currentProgress;
|
||||
infoBox.setText(
|
||||
infoBox
|
||||
.getText()
|
||||
.replaceAll(
|
||||
"Currently processing:.*",
|
||||
String.format(
|
||||
"Processing: Row %d of %d (%.1f%%)",
|
||||
currentY,
|
||||
height,
|
||||
currentProgress * 100
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
Thread.sleep(20); // Add slight delay between notes
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferIndex > 0) {
|
||||
line.write(audioBuffer, 0, bufferIndex);
|
||||
}
|
||||
line.drain();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
infoBox.appendText("\n\nMusic generation complete!");
|
||||
visualizerTimer.stop();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
playThread.start();
|
||||
}
|
||||
|
||||
private byte[] mergeStereoChannels(byte[] left, byte[] right) {
|
||||
byte[] stereo = new byte[left.length * 2];
|
||||
for (int i = 0, j = 0; i < left.length; i += 2, j += 4) {
|
||||
stereo[j] = left[i];
|
||||
stereo[j + 1] = left[i + 1];
|
||||
stereo[j + 2] = right[i];
|
||||
stereo[j + 3] = right[i + 1];
|
||||
}
|
||||
return stereo;
|
||||
}
|
||||
|
||||
private byte[] generateTone(
|
||||
double frequency,
|
||||
int duration,
|
||||
double amplitude
|
||||
) {
|
||||
int numSamples = (duration * SAMPLE_RATE) / 1000;
|
||||
byte[] buffer = new byte[2 * numSamples];
|
||||
|
||||
for (int i = 0; i < numSamples; i++) {
|
||||
double angle = (2.0 * Math.PI * i * frequency) / SAMPLE_RATE;
|
||||
double sample =
|
||||
Math.sin(angle) * 0.5 + // fundamental
|
||||
Math.sin(2 * angle) * 0.1 + // second harmonic
|
||||
Math.sin(4 * angle) * 0.05 + // fourth harmonic
|
||||
Math.sin(angle / 2) * 0.1; // subharmonic
|
||||
|
||||
// Apply envelope for smoother sound
|
||||
double envelope = Math.sin((Math.PI * i) / numSamples);
|
||||
short value = (short) (amplitude *
|
||||
Short.MAX_VALUE *
|
||||
sample *
|
||||
envelope);
|
||||
|
||||
buffer[2 * i] = (byte) (value >> 8);
|
||||
buffer[2 * i + 1] = (byte) (value & 0xFF);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void updateProgress(double value) {
|
||||
progress = value;
|
||||
}
|
||||
|
||||
public void pauseMusic() {
|
||||
isPlaying = false;
|
||||
visualizerTimer.stop();
|
||||
if (line != null) {
|
||||
line.stop();
|
||||
line.flush();
|
||||
}
|
||||
if (playThread != null) {
|
||||
playThread.interrupt();
|
||||
}
|
||||
musicInfoStage.hide();
|
||||
}
|
||||
|
||||
public double getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (line != null) {
|
||||
line.close();
|
||||
}
|
||||
visualizerTimer.stop();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue