Commit a89c030dbaa060bff9805e64c059a5c98d311cec

Authored by pfrison
1 parent b191d7e7

VRGNYMusicLights visualizer Ok

VRGNYMusicLights/Sources/AnimationPlayer.java
... ... @@ -10,14 +10,50 @@ import javax.swing.JPanel;
10 10 public class AnimationPlayer extends JPanel implements ActionListener {
11 11 private static final long serialVersionUID = 1L;
12 12  
  13 + private LightCanvasJPanel lightCanvas;
  14 + private MusicList musicList;
  15 +
13 16 private JButton backward;
14 17 private JButton stop;
15 18 private JButton play;
16 19 private JButton pause;
17 20 private JButton forward;
  21 +
  22 + private long tick = 0;
  23 + private boolean go = false;
  24 + private boolean stopClock = false;
  25 + private static final int TICK_JUMP = 20;
  26 + private Thread tickClock = new Thread(new Runnable() {
  27 + @Override
  28 + public void run() {
  29 + while(!stopClock) {
  30 + if(!go) { // pause
  31 + try { Thread.sleep(100); }
  32 + catch (InterruptedException ignored) {}
  33 + continue;
  34 + }
  35 +
  36 + long before = System.currentTimeMillis();
  37 + lightCanvas.paintLights(musicList.render(tick));
  38 + tick += TICK_JUMP;
  39 + long after = System.currentTimeMillis();
  40 +
  41 + if(after - before < TICK_JUMP) {
  42 + try { Thread.sleep(10 - after + before);}
  43 + catch (InterruptedException ignored) {}
  44 + } else if(after - before > TICK_JUMP) {
  45 + System.err.println("Animation were slowed down by " + String.valueOf(after - before - 10) + " ticks !");
  46 + }
  47 + }
  48 + }
  49 + });
  50 + public void stopTickClock() {stopClock = true;}
18 51  
19   - public AnimationPlayer() { this(BoxLayout.LINE_AXIS); }
20   - public AnimationPlayer(int direction) {
  52 + public AnimationPlayer(LightCanvasJPanel lightCanvas, MusicList musicList) { this(lightCanvas, musicList, BoxLayout.LINE_AXIS); }
  53 + public AnimationPlayer(LightCanvasJPanel lightCanvas, MusicList musicList, int direction) {
  54 + this.lightCanvas = lightCanvas;
  55 + this.musicList = musicList;
  56 +
21 57 setLayout(new BoxLayout(this, direction));
22 58  
23 59 add(Box.createHorizontalGlue());
... ... @@ -48,21 +84,26 @@ public class AnimationPlayer extends JPanel implements ActionListener {
48 84 add(forward);
49 85  
50 86 add(Box.createHorizontalGlue());
  87 +
  88 + tickClock.start();
51 89 }
52 90  
53 91 @Override
54 92 public void actionPerformed(ActionEvent e) {
55 93 // TODO player actions
56 94 if(e.getSource() == backward) {
57   -
  95 + tick -= 500;
  96 + if(tick < 0)
  97 + tick = 0;
58 98 }else if(e.getSource() == stop) {
59   -
  99 + go = false;
  100 + tick = 0;
60 101 }else if(e.getSource() == play) {
61   -
  102 + go = true;
62 103 }else if(e.getSource() == pause) {
63   -
  104 + go = false;
64 105 }else if(e.getSource() == forward) {
65   -
  106 + tick += 500;
66 107 }
67 108 }
68 109 }
... ...
VRGNYMusicLights/Sources/Interface.java
... ... @@ -3,7 +3,6 @@ import java.awt.Font;
3 3 import java.awt.event.WindowAdapter;
4 4 import java.awt.event.WindowEvent;
5 5  
6   -import javax.swing.BoxLayout;
7 6 import javax.swing.JFrame;
8 7 import javax.swing.JLabel;
9 8 import javax.swing.JPanel;
... ... @@ -12,10 +11,13 @@ public class Interface extends JFrame{
12 11 private static final long serialVersionUID = 1L;
13 12 private static final Font titleFont = new Font(new JLabel().getFont().getName(), Font.BOLD, 14);
14 13 private static final Font defaultFont = new Font(new JLabel().getFont().getName(), Font.PLAIN, new JLabel().getFont().getSize());
  14 +
  15 + private MusicList musicList;
  16 + private AnimationPlayer animPlayer;
15 17  
16   - public Interface(final Runnable executeOnClose) {
  18 + public Interface(MusicList musicList, final Runnable executeOnClose) {
17 19 super("VRGNYMusicLights");
18   - //this.actionList = actionList;
  20 + this.musicList = musicList;
19 21 setResizable(false);
20 22 populateWindow();
21 23 pack();
... ... @@ -24,6 +26,8 @@ public class Interface extends JFrame{
24 26 addWindowListener(new WindowAdapter() {
25 27 @Override
26 28 public void windowClosing(WindowEvent e) {
  29 + if(animPlayer != null)
  30 + animPlayer.stopTickClock();
27 31 if(executeOnClose != null)
28 32 executeOnClose.run();
29 33 dispose();
... ... @@ -34,6 +38,7 @@ public class Interface extends JFrame{
34 38 private void populateWindow() {
35 39 JPanel mainPanel = new JPanel(new BorderLayout());
36 40 addPlayPanel(mainPanel);
  41 + addTimeLine(mainPanel);
37 42 add(mainPanel);
38 43 }
39 44  
... ... @@ -41,18 +46,23 @@ public class Interface extends JFrame{
41 46 JPanel playPanel = new JPanel(new BorderLayout());
42 47  
43 48 // title
44   - JLabel title = new JLabel("Player :");
  49 + JLabel title = new JLabel("Visualizer :");
45 50 title.setFont(titleFont);
46 51 playPanel.add(title, BorderLayout.NORTH);
47 52  
48 53 // lights
49   - playPanel.add(new LightCanvasJPanel(), BorderLayout.CENTER);
  54 + LightCanvasJPanel lightCanvas = new LightCanvasJPanel();
  55 + playPanel.add(lightCanvas, BorderLayout.CENTER);
50 56  
51 57 // controls
52   - AnimationPlayer animPlayer = new AnimationPlayer();
  58 + animPlayer = new AnimationPlayer(lightCanvas, musicList);
53 59 animPlayer.setAlignmentX(CENTER_ALIGNMENT);
54 60 playPanel.add(animPlayer, BorderLayout.SOUTH);
55 61  
56 62 parent.add(playPanel, BorderLayout.SOUTH);
57 63 }
  64 +
  65 + private void addTimeLine(JPanel parent) {
  66 + JPanel timeLine = new JPanel();
  67 + }
58 68 }
... ...
VRGNYMusicLights/Sources/LightCanvasJPanel.java
... ... @@ -5,33 +5,29 @@ import java.awt.Graphics;
5 5 import javax.swing.JPanel;
6 6  
7 7 public class LightCanvasJPanel extends JPanel {
8   - public static final double[][] DEFAULT_LIGHT_COORDS = new double[][] {
9   - {0, 0} , {0.2, 0} , {0.4, 0} , {0.6 , 0}, {0.8, 0} , {1, 0} ,
10   - {0, 0.2}, {0.2, 0.2}, {0.4, 0.2}, {0.6, 0.2}, {0.8, 0.2}, {1, 0.2},
11   - {0, 0.4}, {0.2, 0.4}, {0.4, 0.4}, {0.6, 0.4}, {0.8, 0.4}, {1, 0.4},
12   - {0, 0.6}, {0.2, 0.6}, {0.4, 0.6}, {0.6, 0.6}, {0.8, 0.6}, {1, 0.6},
13   - {0, 0.8}, {0.2, 0.8}, {0.4, 0.8}, {0.6, 0.8}, {0.8, 0.8}, {1, 0.8},
14   - {0, 1} , {0.2, 1} , {0.4, 1} , {0.6, 1} , {0.8, 1} , {1, 1} ,
15   - };
16   -
17 8 private static final long serialVersionUID = 1L;
18 9 private static final int WIDTH = 320; // = 300 + padding * 2
19 10 private static final int HEIGHT = 220; // = 200 + padding * 2
20 11 private static final int LIGHT_RADIUS = 4;
21 12 private static final int PADDING = 10;
22 13 private double[][] lightsCoords;
  14 + private double[] lights = null;
23 15  
24   - /**
25   - * @param lightCoords : double[LightID][X = 0, Y = 1] = coordinates as a percentage [0, 1]
26   - */
27   - public LightCanvasJPanel() {this(DEFAULT_LIGHT_COORDS);}
  16 + public LightCanvasJPanel() { this(MusicPath.LIGHT_COORDS); }
28 17 public LightCanvasJPanel(double[][] lightsCoords) {
29 18 this.lightsCoords = lightsCoords;
30 19 setPreferredSize(new Dimension(WIDTH, HEIGHT));
31 20 }
32 21  
  22 + public void paintLights(double[] lights) {
  23 + this.lights = lights;
  24 + repaint();
  25 + }
  26 +
33 27 @Override
34 28 protected void paintComponent(Graphics g) {
  29 + super.paintComponent(g);
  30 +
35 31 // background
36 32 g.setColor(Color.WHITE);
37 33 g.fillRect(0, 0, WIDTH, HEIGHT);
... ... @@ -47,5 +43,21 @@ public class LightCanvasJPanel extends JPanel {
47 43  
48 44 g.fillOval(centerX - LIGHT_RADIUS, centerY - LIGHT_RADIUS, LIGHT_RADIUS * 2, LIGHT_RADIUS * 2);
49 45 }
  46 +
  47 + // lights values
  48 + if(lights == null || lights.length != lightsCoords.length)
  49 + return;
  50 + g.setColor(Color.YELLOW);
  51 + for(int i=0; i<lights.length; i++) {
  52 + if(lightsCoords[i].length != 2)
  53 + throw new MalformedCoordinates();
  54 + int centerX = (int) (lightsCoords[i][0] * (WIDTH - PADDING * 2)) + PADDING;
  55 + int centerY = (int) (lightsCoords[i][1] * (HEIGHT - PADDING * 2)) + PADDING;
  56 +
  57 + g.fillOval((int) (centerX - (LIGHT_RADIUS * lights[i])),
  58 + (int) (centerY - (LIGHT_RADIUS * lights[i])),
  59 + (int) (LIGHT_RADIUS * lights[i] * 2),
  60 + (int) (LIGHT_RADIUS * lights[i] * 2));
  61 + }
50 62 }
51 63 }
... ...
VRGNYMusicLights/Sources/Main.java
  1 +import javax.swing.JOptionPane;
1 2  
2 3 /* TODO list :
3 4 * - keyboard listener for animation player (play / pause = space, forward = semicolon, backward = comma, stop = backspace)
4 5 */
5 6 public class Main {
6 7 public static void main(String[] args) throws InterruptedException {
7   - /* FIXME interface testing in progress
8   -
9   - String serialPort = null;
  8 + // FIXME interface testing in progress
  9 + /*String serialPort = null;
10 10 try {
11 11 serialPort = SerialPortChooserDialog.showSerialPortChooserDialog();
12 12 } catch (NoSerialPortException e) {
... ... @@ -19,7 +19,14 @@ public class Main {
19 19 // arduino need time before accepting serial data
20 20 Thread.sleep(1000);*/
21 21  
22   - Interface i = new Interface(new Runnable() {
  22 + MusicList musicList = new MusicList(null);
  23 + musicList.addMusic(
  24 + new Music(
  25 + new MusicPattern(new int[] {1000, 0, 0, 1000}, 1000, 0),
  26 + new MusicPath(MusicPath.CENTER_IN, 1000),
  27 + 1));
  28 +
  29 + Interface i = new Interface(musicList, new Runnable() {
23 30 @Override
24 31 public void run() {
25 32 //serialCom.close();
... ...
VRGNYMusicLights/Sources/Music.java
1 1  
2 2 public class Music {
3 3 private MusicPattern pattern;
4   - private MusicPath path;
5   - private long startDelai; // in ms
6   - private long length; // in ms
  4 + private int[] lightTickDelais;
  5 + private double volume;
7 6  
8   - public Music(MusicPattern pattern, MusicPath path, long startDelai, long length) {
  7 + public Music(MusicPattern pattern, MusicPath path, double volume) {
9 8 this.pattern = pattern;
10   - this.path = path;
11   - this.startDelai = startDelai;
12   - this.length = length;
13   -
14   - //call MusicPath transform()
  9 + this.lightTickDelais = path.calculateTickDelais();
  10 + this.volume = volume;
  11 + }
  12 +
  13 + public double[] render(long tick) {
  14 + double[] lights = new double[lightTickDelais.length];
  15 + for(int i=0; i<lights.length; i++)
  16 + lights[i] = pattern.render(lightTickDelais[i] + tick) * volume;
  17 + return lights;
15 18 }
16 19 }
... ...
VRGNYMusicLights/Sources/MusicList.java
... ... @@ -8,4 +8,24 @@ public class MusicList {
8 8 this.serialCom = serialCom;
9 9 musics = new ArrayList<>();
10 10 }
  11 +
  12 + public void addMusic(Music music) { musics.add(music); }
  13 + public void deleteMusic(int index) { musics.remove(index); }
  14 +
  15 + public double[] render(long tick) {
  16 + if(musics.size() == 0)
  17 + return null;
  18 + double[] lights = musics.get(0).render(tick);
  19 + for(int i=1; i<musics.size(); i++) {
  20 + double[] tmpRender = musics.get(i).render(tick);
  21 + for(int j=0; j<lights.length; j++) {
  22 + lights[j] += tmpRender[j];
  23 + if(lights[j] < 0)
  24 + lights[j] = 0;
  25 + if(lights[j] > 1)
  26 + lights[j] = 1;
  27 + }
  28 + }
  29 + return lights;
  30 + }
11 31 }
... ...
VRGNYMusicLights/Sources/MusicPath.java
1 1  
2 2 public class MusicPath {
3   - private static final int VERTICAL_DOWN = 0;
4   - private static final int VERTICAL_UP = 1;
5   - private static final int HORIZONTAL_DOWN = 2;
6   - private static final int HORIZONTAL_UP = 3;
7   - private static final int DIAGONAL_TOP_RIGHT_DOWN_LEFT = 4;
8   - private static final int DIAGONAL_TOP_LEFT_DOWN_RIGHT = 5;
9   - private static final int CENTER_OUT = 6;
10   - private static final int CENTER_IN = 7;
  3 + public static final double[][] LIGHT_COORDS = new double[][] {
  4 + // Planche 1
  5 + {0.0383, 0}, {0.0766, 0}, {0.115, 0}, {0.1533, 0}, {0.1916, 0}, {0.23, 0}, //M1L1
  6 + {0.2683, 0}, {0.306, 0}, {0.345, 0}, {0.383, 0}, {0.4216, 0}, {0.46, 0}, {0.4983, 0}, //M1L2
  7 + {0.0333, 0.25}, {0.0666, 0.25}, {0.0999, 0.25}, {0.1333, 0.25}, //P1-1
  8 + {0.3016, 0.25}, {0.3366, 0.25}, {0.3733, 0.25}, {0.4099, 0.25}, {0.4466, 0.25}, {0.4833, 0.25}, {0.5199, 0.25}, //P1-2
  9 + {0.3366, 0.3}, {0.3366, 0.35}, //P1-3
  10 + {0, 0.3075}, {0, 0.365}, {0, 0.4225}, //M1l1
  11 +
  12 + // Planche 2
  13 + {0.5366, 0}, {0.575, 0}, {0.6133, 0}, {0.6516, 0}, {0.69, 0}, {0.7283, 0}, {0.7666, 0}, //M2L1
  14 + {0.805, 0}, {0.8433, 0}, {0.8816, 0}, {0.92, 0}, {0.9583, 0}, //M2L2
  15 + {0.5566, 0.25}, {0.5933, 0.25}, {0.6299, 0.25}, {0.6666, 0.25}, //P2-1
  16 + {0.8366, 0.25}, {0.8699, 0.25}, {0.9032, 0.25}, {0.9366, 0.25}, {0.9699, 0.25}, //P2-2
  17 + {1, 0.06}, {1, 0.115}, {1, 0.17}, {1, 0.225}, {1, 0.28}, {1, 0.335}, {1, 0.39}, //M2l1
  18 +
  19 + // Planche 3
  20 + {0.3366, 0.4}, {0.3366, 0.45}, {0.3366, 0.5}, //P3-1
  21 + {0.3366, 0.75}, {0.3366, 0.8}, //P3-2
  22 + {0, 0.48}, {0, 0.5375}, {0, 0.595}, {0, 0.6525}, {0, 0.71}, {0, 0.7675}, //M3l1
  23 +
  24 + // Planche 4
  25 + {1, 0.445}, {1, 0.5}, {1, 0.555}, {1, 0.61}, {1, 0.665}, {1, 0.72}, {1, 0.775}, //M4l1
  26 +
  27 + // Planche 5
  28 + {0.0383, 1}, {0.0766, 1}, {0.115, 1}, {0.1533, 1}, {0.1916, 1}, {0.23, 1}, //M5L1
  29 + {0.2683, 1}, {0.306, 1}, {0.345, 1}, {0.383, 1}, {0.4216, 1}, {0.46, 1}, {0.4983, 1}, //M5L2
  30 + {0.3366, 0.85}, {0.3366, 0.9}, {0.3366, 0.95}, //P5-1
  31 + {0, 0.8255}, {0, 0.8835}, {0, 0.9415}, //M5l1
  32 +
  33 + // Planche 6
  34 + {0.5366, 1}, {0.575, 1}, {0.6133, 1}, {0.6516, 1}, {0.69, 1}, {0.7283, 1}, {0.7666, 1}, //M6L1
  35 + {0.805, 1}, {0.8433, 1}, {0.8816, 1}, {0.92, 1}, {0.9583, 1}, //M6L2
  36 + {1, 0.83}, {1, 0.885}, {1, 0.94} //M6l1
  37 + };
  38 +
  39 + public static final int VERTICAL_DOWN = 0;
  40 + public static final int VERTICAL_UP = 1;
  41 + public static final int HORIZONTAL_TO_LEFT = 2;
  42 + public static final int HORIZONTAL_TO_RIGHT = 3;
  43 + public static final int DIAGONAL_TOP_RIGHT_DOWN_LEFT = 4;
  44 + public static final int DIAGONAL_DOWN_LEFT_TOP_RIGHT = 5;
  45 + public static final int DIAGONAL_TOP_LEFT_DOWN_RIGHT = 6;
  46 + public static final int DIAGONAL_DOWN_RIGHT_TOP_LEFT = 7;
  47 + public static final int CENTER_OUT = 8;
  48 + public static final int CENTER_IN = 9;
  49 + public static final int ALL = 10;
11 50  
12 51 private int animation;
  52 + private int effectDuration;
  53 + private double[][] lightsCoords;
13 54  
14   - public MusicPath(int animation) {
  55 + public MusicPath(int animation, int effectDuration) { this(animation, effectDuration, LIGHT_COORDS); }
  56 + public MusicPath(int animation, int effectDuration, double[][] lightsCoords) {
15 57 this.animation = animation;
  58 + this.effectDuration = effectDuration;
  59 + this.lightsCoords = lightsCoords;
16 60 }
17 61  
18   - // TODO transform music according to animation
19   - // what format ? int[nled][pattern] (ms) ?
  62 + public int[] calculateTickDelais() { // tick in ms
  63 + int[] ticksDelais = new int[lightsCoords.length];
  64 + for(int i=0; i<ticksDelais.length; i++) {
  65 + ticksDelais[i] = 0;
  66 + if(animation == VERTICAL_DOWN
  67 + || animation == VERTICAL_UP
  68 + || animation == HORIZONTAL_TO_LEFT
  69 + || animation == HORIZONTAL_TO_RIGHT
  70 + || animation == DIAGONAL_TOP_RIGHT_DOWN_LEFT
  71 + || animation == DIAGONAL_DOWN_LEFT_TOP_RIGHT
  72 + || animation == DIAGONAL_TOP_LEFT_DOWN_RIGHT
  73 + || animation == DIAGONAL_DOWN_RIGHT_TOP_LEFT
  74 + || animation == CENTER_OUT
  75 + || animation == CENTER_IN) {
  76 + switch (animation) {
  77 + case VERTICAL_DOWN:
  78 + ticksDelais[i] += tickLateVerticalDown(LIGHT_COORDS[i][1]);
  79 + break;
  80 + case VERTICAL_UP:
  81 + ticksDelais[i] += tickLateVerticalUp(LIGHT_COORDS[i][1]);
  82 + break;
  83 + case HORIZONTAL_TO_LEFT:
  84 + ticksDelais[i] += tickLateHorizontalToLeft(LIGHT_COORDS[i][0]);
  85 + break;
  86 + case HORIZONTAL_TO_RIGHT:
  87 + ticksDelais[i] += tickLateHorizontalToRight(LIGHT_COORDS[i][0]);
  88 + break;
  89 + case DIAGONAL_TOP_RIGHT_DOWN_LEFT:
  90 + ticksDelais[i] += tickLateDiagTRDL(LIGHT_COORDS[i][0], LIGHT_COORDS[i][1]);
  91 + break;
  92 + case DIAGONAL_DOWN_LEFT_TOP_RIGHT:
  93 + ticksDelais[i] += tickLateDiagDLTR(LIGHT_COORDS[i][0], LIGHT_COORDS[i][1]);
  94 + break;
  95 + case DIAGONAL_TOP_LEFT_DOWN_RIGHT:
  96 + ticksDelais[i] += tickLateDiagTLDR(LIGHT_COORDS[i][0], LIGHT_COORDS[i][1]);
  97 + break;
  98 + case DIAGONAL_DOWN_RIGHT_TOP_LEFT:
  99 + ticksDelais[i] += tickLateDiagDRTL(LIGHT_COORDS[i][0], LIGHT_COORDS[i][1]);
  100 + break;
  101 + case CENTER_OUT:
  102 + ticksDelais[i] += tickLateCenterOut(LIGHT_COORDS[i][0], LIGHT_COORDS[i][1]);
  103 + break;
  104 + case CENTER_IN:
  105 + ticksDelais[i] += tickLateCenterIn(LIGHT_COORDS[i][0], LIGHT_COORDS[i][1]);
  106 + break;
  107 + }
  108 + }
  109 + else if(animation == ALL);
  110 + }
  111 + return ticksDelais;
  112 + }
  113 +
  114 + private int tickLateVerticalDown(double y) {return (int) ((1-y) * effectDuration);}
  115 + private int tickLateVerticalUp(double y) {return (int) (y * effectDuration);}
  116 + private int tickLateHorizontalToRight(double x) {return (int) ((1-x) * effectDuration);}
  117 + private int tickLateHorizontalToLeft(double x) {return (int) (x * effectDuration);}
  118 + private int tickLateDiagTRDL(double x, double y) {return (int) ((x - y) / 2d * (double) effectDuration);}
  119 + private int tickLateDiagDLTR(double x, double y) {return (int) ((-x + y) / 2d * (double) effectDuration);}
  120 + private int tickLateDiagDRTL(double x, double y) {return (int) ((x + y) / 2d * (double) effectDuration);}
  121 + private int tickLateDiagTLDR(double x, double y) {return (int) ((-x - y) / 2d * (double) effectDuration);}
  122 + private int tickLateCenterOut(double x, double y) {return (int) ((1 - (x-0.5)*(x-0.5) - (y-0.5)*(y-0.5)) * 2 * (double) effectDuration);}
  123 + private int tickLateCenterIn(double x, double y) {return (int) (((x-0.5)*(x-0.5) + (y-0.5)*(y-0.5)) * 2 * (double) effectDuration);}
  124 +
  125 + public int getNumberOfLights() {
  126 + return this.lightsCoords.length;
  127 + }
20 128 }
... ...
VRGNYMusicLights/Sources/MusicPattern.java
... ... @@ -7,10 +7,35 @@ public class MusicPattern {
7 7 * 3 : fall time
8 8 */
9 9 private int[] pattern; // in ms
  10 + private int totalLentgh;
  11 + private int[] patternTiming = new int[4]; // in ms
  12 + private long begin; // in ms
  13 + private long stop; // in ms
10 14  
11   - public MusicPattern(int[] pattern) {
  15 + public MusicPattern(int[] pattern, int repeat, long begin) {
12 16 if(pattern.length != 4)
13 17 throw new InvalidPatternException();
14 18 this.pattern = pattern;
  19 + this.totalLentgh = pattern[0] + pattern[1] + pattern[2] + pattern[3];
  20 + this.patternTiming[0] = pattern[0];
  21 + for(int i=1; i<4; i++)
  22 + this.patternTiming[i] = this.patternTiming[i-1] + pattern[i];
  23 + this.begin = begin;
  24 + this.stop = repeat * totalLentgh;
  25 + }
  26 +
  27 + public double render(long tick) { // tick in ms
  28 + if(tick <= begin)
  29 + return 0;
  30 + if(tick >= stop)
  31 + return 0;
  32 + int tickMod = (int) (tick % totalLentgh);
  33 + if(tickMod < patternTiming[0])
  34 + return 0;
  35 + if(tickMod < patternTiming[1])
  36 + return (double) (tickMod - patternTiming[0]) / (double) (pattern[1]);
  37 + if(tickMod < patternTiming[2])
  38 + return 1;
  39 + return 1 - ((double) (tickMod - patternTiming[2]) / (double) (pattern[3]));
15 40 }
16 41 }
... ...