From 186f8626563d9c532fb69d806c6a5665220e20a8 Mon Sep 17 00:00:00 2001 From: SuspiciousActivity <38253929+SuspiciousActivity@users.noreply.github.com> Date: Mon, 12 Sep 2022 00:08:02 +0200 Subject: [PATCH] Add files via upload --- src/me/SoundDraw/AudioPlayer.java | 89 ++++++++++++++ src/me/SoundDraw/DrawingBoard.java | 151 ++++++++++++++++++++++++ src/me/SoundDraw/IntToByteFunction.java | 6 + src/me/SoundDraw/Main.java | 139 ++++++++++++++++++++++ 4 files changed, 385 insertions(+) create mode 100644 src/me/SoundDraw/AudioPlayer.java create mode 100644 src/me/SoundDraw/DrawingBoard.java create mode 100644 src/me/SoundDraw/IntToByteFunction.java create mode 100644 src/me/SoundDraw/Main.java diff --git a/src/me/SoundDraw/AudioPlayer.java b/src/me/SoundDraw/AudioPlayer.java new file mode 100644 index 0000000..ad77f5b --- /dev/null +++ b/src/me/SoundDraw/AudioPlayer.java @@ -0,0 +1,89 @@ +package me.SoundDraw; + +import java.io.IOException; +import java.io.InputStream; +import java.util.function.IntSupplier; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.SourceDataLine; + +public class AudioPlayer extends Thread { + + private final IntToByteFunction func; + private final IntSupplier volume; + + private final AudioFormat format; + private final SourceDataLine sourceDataLine; + + public AudioPlayer(IntToByteFunction func, IntSupplier volume) throws LineUnavailableException { + this.func = func; + this.volume = volume; + + format = new AudioFormat(Main.SAMPLE_RATE, 8, 1, true, false); + DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, format); + sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo); + } + + @Override + public void run() { + try { + sourceDataLine.open(format); + } catch (LineUnavailableException e) { + throw new RuntimeException(e); + } + sourceDataLine.start(); + + try (AudioGen gen = new AudioGen()) { + byte[] data = new byte[4096]; + int read = 0; + + while ((read = gen.read(data)) > 0) { + sourceDataLine.write(data, 0, read); + } + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + sourceDataLine.drain(); + sourceDataLine.close(); + } + } + + private class AudioGen extends InputStream { + + private int index; + + @Override + public int read() throws IOException { + return (int) (func.apply(index++) * volume.getAsInt() / 100); + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int c = read(); + b[off] = (byte) c; + + int i = 1; + try { + for (; i < len; i++) { + c = read(); + b[off + i] = (byte) c; + } + } catch (IOException ee) { + } + return i; + } + + } + +} diff --git a/src/me/SoundDraw/DrawingBoard.java b/src/me/SoundDraw/DrawingBoard.java new file mode 100644 index 0000000..6666f25 --- /dev/null +++ b/src/me/SoundDraw/DrawingBoard.java @@ -0,0 +1,151 @@ +package me.SoundDraw; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ContainerAdapter; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Arrays; + +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +public class DrawingBoard extends JPanel { + + private static final int STEP = 100; + private static final int DEFAULT_SIZE = STEP * 5; + + private final MouseListener listener; + private byte[] data = new byte[DEFAULT_SIZE]; + private int lastEditedSize = DEFAULT_SIZE; + + private boolean down = false; + private Point lastPos; + + public DrawingBoard() { + listener = new MouseListener(); + setBackground(Color.black); + addMouseListener(listener); + addMouseMotionListener(listener); + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + repaint(); + } + }); + } + + public byte getSampleAt(int idx) { + return data[idx % data.length]; + } + + public int resizeData(int mul) { + int size = mul * STEP; + + if (data.length == size) + return -1; + + int oldSize = data.length; + data = Arrays.copyOf(data, size); + + if (oldSize < size) { + for (int i = lastEditedSize; i + lastEditedSize <= size; i += lastEditedSize) { + System.arraycopy(data, 0, data, i, lastEditedSize); + } + } else if (size < lastEditedSize) { + lastEditedSize = size; + } + + SwingUtilities.invokeLater(this::repaint); + + return size; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + int width = getWidth(); + int height = getHeight(); + + g.setColor(Color.darkGray); + g.drawLine(0, height / 2, width, height / 2); + + g.setColor(Color.white); + for (int i = 0; i < data.length - 1; i++) { + g.drawLine(i * width / data.length, (data[i] + 128) * height / 256, (i + 1) * width / data.length, + (data[i + 1] + 128) * height / 256); + } + } + + private class MouseListener extends MouseAdapter { + @Override + public void mousePressed(MouseEvent e) { + down = true; + lastPos = e.getPoint(); + firePaint(e.getPoint()); + e.consume(); + } + + @Override + public void mouseReleased(MouseEvent e) { + firePaint(e.getPoint()); + down = false; + lastPos = null; + e.consume(); + } + + @Override + public void mouseDragged(MouseEvent e) { + firePaint(e.getPoint()); + e.consume(); + } + + private void firePaint(Point pos) { + if (!down) + return; + lastEditedSize = data.length; + + int width = getWidth(); + int height = getHeight(); + int stepHalfX = width / data.length / 2; + int stepHalfY = height / 256 / 2; + + if (pos.y < 0) + pos.y = 0; + else if (pos.y >= height) + pos.y = height - 1; + + if (lastPos == null) + lastPos = pos; + + try { + Point pLow = pos.x < lastPos.x ? pos : lastPos; + Point pHigh = pos.x < lastPos.x ? lastPos : pos; + + int lastIdx = (pLow.x + stepHalfX) * data.length / width; + int newIdx = (pHigh.x + stepHalfX) * data.length / width; + int lastVal = pLow.y; + int newVal = pHigh.y; + int diffIdx = newIdx - lastIdx + 1; + float diff = newVal - lastVal; + + for (int curIdx = lastIdx, off = 0; (curIdx = lastIdx + off) <= newIdx; off++) { + if (curIdx < 0 || curIdx >= data.length) + continue; + int val = Math.round(lastVal + diff * off / diffIdx); + data[curIdx] = (byte) (((val * 256 + stepHalfY) / height) - 128); + curIdx++; + } + + repaint(); + } finally { + lastPos = pos; + } + } + } + +} diff --git a/src/me/SoundDraw/IntToByteFunction.java b/src/me/SoundDraw/IntToByteFunction.java new file mode 100644 index 0000000..9907940 --- /dev/null +++ b/src/me/SoundDraw/IntToByteFunction.java @@ -0,0 +1,6 @@ +package me.SoundDraw; + +@FunctionalInterface +public interface IntToByteFunction { + byte apply(int value); +} \ No newline at end of file diff --git a/src/me/SoundDraw/Main.java b/src/me/SoundDraw/Main.java new file mode 100644 index 0000000..69a7ee9 --- /dev/null +++ b/src/me/SoundDraw/Main.java @@ -0,0 +1,139 @@ +package me.SoundDraw; + +import java.awt.EventQueue; +import java.awt.Toolkit; +import java.util.Locale; + +import javax.sound.sampled.LineUnavailableException; +import javax.swing.JFrame; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import javax.swing.JSlider; +import javax.swing.UIManager; +import javax.swing.JLabel; +import javax.swing.GroupLayout; +import javax.swing.GroupLayout.Alignment; +import java.awt.FlowLayout; +import javax.swing.border.LineBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import java.awt.Color; +import javax.swing.SpringLayout; +import javax.swing.BoxLayout; +import java.awt.Component; +import javax.swing.Box; + +public class Main { + + public static final int SAMPLE_RATE = 44100; + + private JFrame frmAudiodraw; + + /** + * Launch the application. + */ + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + Main window = new Main(); + window.frmAudiodraw.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + /** + * Create the application. + * + * @throws LineUnavailableException + */ + public Main() throws LineUnavailableException { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e1) { + e1.printStackTrace(); + } + initialize(); + } + + /** + * Initialize the contents of the frame. + * + * @throws LineUnavailableException + */ + private void initialize() throws LineUnavailableException { + frmAudiodraw = new JFrame(); + frmAudiodraw.getContentPane().setBackground(Color.DARK_GRAY); + frmAudiodraw.setTitle("Sound Draw"); + frmAudiodraw.setBounds((int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2 - 250), + (int) (Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2 - 150), 500, 300); + frmAudiodraw.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + SpringLayout springLayout = new SpringLayout(); + frmAudiodraw.getContentPane().setLayout(springLayout); + + DrawingBoard board = new DrawingBoard(); + springLayout.putConstraint(SpringLayout.WEST, board, 10, SpringLayout.WEST, frmAudiodraw.getContentPane()); + springLayout.putConstraint(SpringLayout.SOUTH, board, -10, SpringLayout.SOUTH, frmAudiodraw.getContentPane()); + springLayout.putConstraint(SpringLayout.EAST, board, -10, SpringLayout.EAST, frmAudiodraw.getContentPane()); + frmAudiodraw.getContentPane().add(board); + + JPanel controls = new JPanel(); + controls.setBackground(Color.DARK_GRAY); + springLayout.putConstraint(SpringLayout.NORTH, controls, 10, SpringLayout.NORTH, frmAudiodraw.getContentPane()); + springLayout.putConstraint(SpringLayout.WEST, controls, 10, SpringLayout.WEST, frmAudiodraw.getContentPane()); + springLayout.putConstraint(SpringLayout.EAST, controls, -10, SpringLayout.EAST, frmAudiodraw.getContentPane()); + springLayout.putConstraint(SpringLayout.NORTH, board, 10, SpringLayout.SOUTH, controls); + frmAudiodraw.getContentPane().add(controls); + + JPanel panelVolume = new JPanel(); + panelVolume.setBackground(Color.DARK_GRAY); + panelVolume.setLayout(new BoxLayout(panelVolume, BoxLayout.X_AXIS)); + + JLabel lblVolume = new JLabel("Volume"); + lblVolume.setForeground(Color.WHITE); + panelVolume.add(lblVolume); + + JSlider sliderVolume = new JSlider(); + sliderVolume.setBackground(Color.DARK_GRAY); + panelVolume.add(sliderVolume); + sliderVolume.setFocusable(false); + sliderVolume.setValue(10); + + JPanel panelLength = new JPanel(); + panelLength.setBackground(Color.DARK_GRAY); + panelLength.setLayout(new BoxLayout(panelLength, BoxLayout.X_AXIS)); + + JLabel lblLength = new JLabel("Length (0.05s)"); + lblLength.setForeground(Color.WHITE); + panelLength.add(lblLength); + + JSlider sliderLength = new JSlider(); + sliderLength.setMaximum(SAMPLE_RATE / 100 * 2); + sliderLength.setBackground(Color.DARK_GRAY); + sliderLength.setSnapToTicks(true); + sliderLength.setValue(22); + sliderLength.setMinimum(22); + panelLength.add(sliderLength); + sliderLength.setFocusable(false); + sliderLength.addChangeListener(e -> { + int size = board.resizeData(sliderLength.getValue()); + if (size != -1) { + lblLength + .setText("Length (" + String.format(Locale.ENGLISH, "%.2fs", size / (float) SAMPLE_RATE) + ")"); + } + }); + controls.setLayout(new BoxLayout(controls, BoxLayout.X_AXIS)); + controls.add(panelVolume); + + Component spacer = Box.createHorizontalStrut(20); + controls.add(spacer); + controls.add(panelLength); + + AudioPlayer player = new AudioPlayer(board::getSampleAt, sliderVolume::getValue); + player.start(); + } +}