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
		Add a link
		
	
		Reference in a new issue