From 683924d18c6487d6d61ea8ad8f8d92a7a391ab0a Mon Sep 17 00:00:00 2001 From: zuim Date: Tue, 23 Feb 2021 02:41:52 +0100 Subject: [PATCH] version 1.0 --- .idea/.gitignore | 3 + .idea/artifacts/LEDControl_jar.xml | 15 ++ .idea/compiler.xml | 13 ++ .idea/jarRepositories.xml | 20 ++ .idea/misc.xml | 14 ++ .idea/uiDesigner.xml | 124 ++++++++++++ .idea/vcs.xml | 6 + LEDControl.iml | 2 + pom.xml | 33 ++++ .../de/zuim/ledcontrol/EffectManager.java | 57 ++++++ .../java/de/zuim/ledcontrol/LEDControl.java | 108 +++++++++++ .../java/de/zuim/ledcontrol/LEDEffect.java | 12 ++ .../zuim/ledcontrol/effects/AudioEffect.java | 179 ++++++++++++++++++ .../zuim/ledcontrol/effects/ClockEffect.java | 49 +++++ .../zuim/ledcontrol/effects/ColorSweep.java | 26 +++ .../zuim/ledcontrol/effects/SineEffect.java | 53 ++++++ .../ledcontrol/effects/TemperatureEffect.java | 58 ++++++ src/main/resources/META-INF/MANIFEST.MF | 3 + src/main/resources/icon.png | Bin 0 -> 6333 bytes 19 files changed, 775 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/artifacts/LEDControl_jar.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 LEDControl.iml create mode 100644 pom.xml create mode 100644 src/main/java/de/zuim/ledcontrol/EffectManager.java create mode 100644 src/main/java/de/zuim/ledcontrol/LEDControl.java create mode 100644 src/main/java/de/zuim/ledcontrol/LEDEffect.java create mode 100644 src/main/java/de/zuim/ledcontrol/effects/AudioEffect.java create mode 100644 src/main/java/de/zuim/ledcontrol/effects/ClockEffect.java create mode 100644 src/main/java/de/zuim/ledcontrol/effects/ColorSweep.java create mode 100644 src/main/java/de/zuim/ledcontrol/effects/SineEffect.java create mode 100644 src/main/java/de/zuim/ledcontrol/effects/TemperatureEffect.java create mode 100644 src/main/resources/META-INF/MANIFEST.MF create mode 100644 src/main/resources/icon.png diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/artifacts/LEDControl_jar.xml b/.idea/artifacts/LEDControl_jar.xml new file mode 100644 index 0000000..a2e19f5 --- /dev/null +++ b/.idea/artifacts/LEDControl_jar.xml @@ -0,0 +1,15 @@ + + + $PROJECT_DIR$/out/artifacts/LEDControl_jar + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fdf34bc --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e6ee860 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LEDControl.iml b/LEDControl.iml new file mode 100644 index 0000000..78b2cc5 --- /dev/null +++ b/LEDControl.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..135b37e --- /dev/null +++ b/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + org.example + LEDControl + 1.0-SNAPSHOT + + + 15 + 15 + + + + + com.fazecast + jSerialComm + [2.0.0,3.0.0) + + + com.profesorfalken + jSensors + 2.2.1 + + + com.tagtraum + jipes + 0.9.17 + + + \ No newline at end of file diff --git a/src/main/java/de/zuim/ledcontrol/EffectManager.java b/src/main/java/de/zuim/ledcontrol/EffectManager.java new file mode 100644 index 0000000..3f007e2 --- /dev/null +++ b/src/main/java/de/zuim/ledcontrol/EffectManager.java @@ -0,0 +1,57 @@ +package de.zuim.ledcontrol; + +import de.zuim.ledcontrol.effects.*; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +public class EffectManager { + + int activeId=4; + private final LEDEffect[] effects = new LEDEffect[]{new TemperatureEffect(), new ClockEffect(), new SineEffect(), new AudioEffect(), new ColorSweep()}; + + public EffectManager(){ + + final SystemTray tray = SystemTray.getSystemTray(); + final TrayIcon trayIcon = new TrayIcon(new ImageIcon(getClass().getResource("/icon.png")).getImage(),"LEDEffects"); + + trayIcon.addMouseListener(new MouseListener() { + @Override + public void mouseClicked(MouseEvent e) { + if(e.getButton()!=MouseEvent.BUTTON1) + System.exit(0); + getActiveEffect().unload(); + activeId++; + if(activeId==effects.length) + activeId=0; + getActiveEffect().load(); + trayIcon.setToolTip("LEDEffects - " + getActiveEffect().getDescription()); + } + + @Override + public void mousePressed(MouseEvent e) {} + + @Override + public void mouseReleased(MouseEvent e) {} + + @Override + public void mouseEntered(MouseEvent e) {} + + @Override + public void mouseExited(MouseEvent e) {} + }); + try { + tray.add(trayIcon); + } catch (AWTException e) { + e.printStackTrace(); + } + + getActiveEffect().load(); + } + + public LEDEffect getActiveEffect() { + return effects[activeId]; + } +} diff --git a/src/main/java/de/zuim/ledcontrol/LEDControl.java b/src/main/java/de/zuim/ledcontrol/LEDControl.java new file mode 100644 index 0000000..f762618 --- /dev/null +++ b/src/main/java/de/zuim/ledcontrol/LEDControl.java @@ -0,0 +1,108 @@ +package de.zuim.ledcontrol; + +import com.fazecast.jSerialComm.SerialPort; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; + +public class LEDControl { + public static final int WIDTH=16; + public static final int HEIGHT=16; + public static final int LED_NUM = WIDTH*HEIGHT; + public static final int BAUD = 1000000; + + + private SerialPort port; + private byte[][][] leds = new byte[WIDTH][HEIGHT][3]; + private EffectManager eff; + private BufferedImage scaledImage; + + public LEDControl(){ + eff = new EffectManager(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> port.closePort())); + + connect(); + + sendLoop(); + } + + private void sendLoop(){ + long time = System.nanoTime(); + long iteration = 0; + + while(true){ + if(port == null || !port.isOpen()){ + sleep(1000); + connect(); + continue; + } + + long timeDelta = System.nanoTime()-time; + time = System.nanoTime(); + + renderFrame(timeDelta); + + port.writeBytes(new byte[]{(byte) 255}, 1); //start of new frame + + for(int x=0;x>16)&0xff),254); + leds[x][y][1] = (byte) Math.min((col>>8)&0xff,254); + leds[x][y][2] = (byte) Math.min(((col)&0xff),254); + port.writeBytes(leds[x][y], 3); + } + } + + sleep(10); + + if(iteration%1000==1){ + System.out.println("Frametime: "+ (Math.round(timeDelta)/100000)/10.0 + "ms ("+iteration+" Frames)"); + } + iteration++; + } + } + + private void connect(){ + SerialPort[] ports = SerialPort.getCommPorts(); + if(ports.length > 0){ + port = SerialPort.getCommPorts()[0]; + System.out.println("Connect to " + port.getDescriptivePortName()+" "+port.getSystemPortName()+" "); + port.setComPortParameters(BAUD,8,1,0); + port.openPort(); + } + } + + private void sleep(int ms){ + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private void renderFrame(long timeDelta) { + BufferedImage image = new BufferedImage(16*eff.getActiveEffect().getScale(),16*eff.getActiveEffect().getScale(),BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + eff.getActiveEffect().render(timeDelta, g); + g.dispose(); + + if(eff.getActiveEffect().getScale()==1){ + scaledImage = image; + }else{ + scaledImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); + AffineTransform at = new AffineTransform(); + at.scale(1.0/eff.getActiveEffect().getScale(), 1.0/eff.getActiveEffect().getScale()); + AffineTransformOp scaleOp = + new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); + scaledImage = scaleOp.filter(image, scaledImage); + } + } + + public static void main(String[] args) { + new LEDControl(); + } +} diff --git a/src/main/java/de/zuim/ledcontrol/LEDEffect.java b/src/main/java/de/zuim/ledcontrol/LEDEffect.java new file mode 100644 index 0000000..f79f228 --- /dev/null +++ b/src/main/java/de/zuim/ledcontrol/LEDEffect.java @@ -0,0 +1,12 @@ +package de.zuim.ledcontrol; + +import java.awt.*; + +public interface LEDEffect { + + String getDescription(); + default int getScale() {return 1; } + default void load() {} + default void unload() {} + void render(long timeDelta, Graphics g); +} diff --git a/src/main/java/de/zuim/ledcontrol/effects/AudioEffect.java b/src/main/java/de/zuim/ledcontrol/effects/AudioEffect.java new file mode 100644 index 0000000..996b183 --- /dev/null +++ b/src/main/java/de/zuim/ledcontrol/effects/AudioEffect.java @@ -0,0 +1,179 @@ +package de.zuim.ledcontrol.effects; + +import com.tagtraum.jipes.math.FFTFactory; +import com.tagtraum.jipes.math.Transform; +import de.zuim.ledcontrol.LEDControl; +import de.zuim.ledcontrol.LEDEffect; + +import javax.sound.sampled.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static de.zuim.ledcontrol.LEDControl.HEIGHT; +import static de.zuim.ledcontrol.LEDControl.WIDTH; + +public class AudioEffect implements LEDEffect { + @Override + public String getDescription() { + return "Audio Effect"; + } + + final static int BUFFERSIZE = 2048; + double[] magnitudes = null; + Thread audioThread = null; + + @Override + public void load() { + Runnable audioRunnable = () -> { + + + Mixer.Info[] mixers = AudioSystem.getMixerInfo(); + List availableTargetLines = new ArrayList<>(); + for (Mixer.Info mixerInfo : mixers){ + + Mixer m = AudioSystem.getMixer(mixerInfo); + + Line.Info[] lines = m.getTargetLineInfo(); + + for (Line.Info li : lines) + { + try + { + if(li instanceof DataLine.Info && mixerInfo.toString().contains("mix")) + { + m.open(); + System.out.println("("+availableTargetLines.size()+") Found target line: " + li+" "+mixerInfo + "("+li.getClass()+")"); + availableTargetLines.add(li); + m.close(); + } + } catch (LineUnavailableException e){ + System.out.println("Line unavailable."); + } + } + } + + DataLine.Info targetLineInfo = (DataLine.Info) availableTargetLines.get(0); + + System.out.println("SUPPORTED TARGET FORMATS: "); + AudioFormat[] formats = (targetLineInfo).getFormats(); + for(int i=0; i decode + + float[] fbuf = decode(buf,format); + + final float[][] transformed = fft.transform(fbuf); + final float[] realPart = transformed[0]; + final float[] imaginaryPart = transformed[1]; + magnitudes = toMagnitudes(realPart, imaginaryPart); + + //System.out.println("M"+ Arrays.toString(magnitudes)); + // do something with magnitudes... + int max = 0, avg = 0,min=11111111; + for(double m : magnitudes){ + avg += m; + if(m>max) + max= (int) m; + if(m 1) + step=0; + + gr.setColor(Color.getHSBColor(step,1f,15f/255f)); + gr.fillRect(0,0,WIDTH,HEIGHT); + } +} diff --git a/src/main/java/de/zuim/ledcontrol/effects/SineEffect.java b/src/main/java/de/zuim/ledcontrol/effects/SineEffect.java new file mode 100644 index 0000000..5c88498 --- /dev/null +++ b/src/main/java/de/zuim/ledcontrol/effects/SineEffect.java @@ -0,0 +1,53 @@ +package de.zuim.ledcontrol.effects; + +import de.zuim.ledcontrol.LEDEffect; + +import java.awt.*; +import java.util.Random; + +import static de.zuim.ledcontrol.LEDControl.HEIGHT; +import static de.zuim.ledcontrol.LEDControl.WIDTH; + +public class SineEffect implements LEDEffect { + + private Random r = new Random(); + private double[][] seeds = new double[10][7]; + + @Override + public void load() { + for(double[] seed : seeds){ + for(int i = 0; i < seed.length; i++){ + seed[i] = r.nextDouble(); + } + } + } + + double steps = 0; + @Override + public void render(long timeDelta, Graphics g) { + + steps += timeDelta*0.000000001; + + for(int i = 0; i < 6; i++){ + g.setColor(new Color((int) (15*seeds[i][0]), (int) (10*seeds[i][3]),(int)(4*seeds[i][4]))); + for (int x = 0; x < WIDTH*getScale(); x++){ + g.drawLine(x, calc(i, x),x+1,calc(i, x+1)); + } + } + } + + private int calc(int i, int x){ + return (int) (Math.sin(((x+2*seeds[i][2])*0.5*seeds[i][1])/getScale()+steps*seeds[i][6])*7*getScale()*(seeds[i][5]+0.2) + (getScale()*HEIGHT)/2); + } + + @Override + public String getDescription() { + return "SineEffect"; + } + + @Override + public int getScale() { + return 1; + } + +} diff --git a/src/main/java/de/zuim/ledcontrol/effects/TemperatureEffect.java b/src/main/java/de/zuim/ledcontrol/effects/TemperatureEffect.java new file mode 100644 index 0000000..786aa1a --- /dev/null +++ b/src/main/java/de/zuim/ledcontrol/effects/TemperatureEffect.java @@ -0,0 +1,58 @@ +package de.zuim.ledcontrol.effects; + +import com.profesorfalken.jsensors.JSensors; +import com.profesorfalken.jsensors.model.components.Component; +import com.profesorfalken.jsensors.model.components.Components; +import com.profesorfalken.jsensors.model.components.Cpu; +import com.profesorfalken.jsensors.model.sensors.Fan; +import com.profesorfalken.jsensors.model.sensors.Load; +import com.profesorfalken.jsensors.model.sensors.Temperature; +import de.zuim.ledcontrol.LEDEffect; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + + +public class TemperatureEffect implements LEDEffect { + @Override + public String getDescription() { + return "Temperatur Sensoren"; + } + + @Override + public void render(long timeDelta, Graphics g) { + /*Components components = JSensors.get.components(); + + List comps = new ArrayList<>(components.cpus); + comps.addAll(components.disks); + comps.addAll(components.gpus); + comps.addAll(components.mobos); + + if (comps != null) { + for (final Component c : comps) { + System.out.println("Found component: " + c.name); + if (c.sensors != null) { + System.out.println("Sensors: "); + + //Print temperatures + List temps = c.sensors.temperatures; + for (final Temperature temp : temps) { + System.out.println(temp.name + ": " + temp.value + " C"); + } + + //Print fan speed + List fans = c.sensors.fans; + for (final Fan fan : fans) { + System.out.println(fan.name + ": " + fan.value + " RPM"); + } + //Print fan speed + List loads = c.sensors.loads; + for (final Load load : loads) { + System.out.println(load.name + ": " + load.value + " %"); + } + } + } + }*/ + } +} diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..8f32fa0 --- /dev/null +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: de.zuim.ledcontrol.LEDControl + diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..35cbec276cca9d5411175bc49e9ae4d3a8b745db GIT binary patch literal 6333 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 literal 0 HcmV?d00001