diff --git a/.classpath b/.classpath index 21de87f..8c2bcd3 100644 --- a/.classpath +++ b/.classpath @@ -10,5 +10,6 @@ + diff --git a/src/animata/Animata.java b/src/animata/Animata.java new file mode 100644 index 0000000..36ca7ff --- /dev/null +++ b/src/animata/Animata.java @@ -0,0 +1,72 @@ +package animata; + +import oscP5.OscMessage; +import oscP5.OscP5; +import processing.core.PApplet; +import netP5.NetAddress; +import netP5.NetInfo; + +public class Animata { + + public static NetAddress net; + public static OscP5 oscP5; + + public static void startOSC(PApplet applet) { + net = new NetAddress(NetInfo.getHostAddress(), 7110); + oscP5 = new OscP5(applet, 12000); + PApplet.println("Sending Animata stuff to " + net.address()); + } + + + private static boolean checkInitialised() { + if (net == null) { + //PApplet.println("need to call init() with valid settings on Animata"); + return false; + } + return true; + } + + public static void zoomCamera(Float delta) { + if (!checkInitialised()) return; + OscMessage message = new OscMessage("/cameradeltazoom"); + message.add(delta); + oscP5.send(message, net); + System.out.println("message:" + message + " to " + net); + } + + public static void panLayer(Float deltaX) { + if (!checkInitialised()) return; + OscMessage message = new OscMessage("/cameradeltapan"); + message.add(deltaX); + message.add(0.0f); + oscP5.send(message, net); + System.out.println("message:" + message + " to " + net); + } + + public static void setBone(String name, Float n) { + if (!checkInitialised()) return; + OscMessage message = new OscMessage("/anibone"); + message.add(name); + message.add((float) n); + PApplet.println("trying to send to " + net.address() + " = " + n + " to bone " + name); + oscP5.send(message, net); + } + + public static void setAlpha(String layer, float value) { + if(!checkInitialised()) return; + OscMessage message = new OscMessage("/layeralpha"); + message.add(layer); + message.add(value); + oscP5.send(message, net); + } + + public static void setBoneTempo(String bone, Float tempo) { + if(!checkInitialised()) return; + OscMessage message = new OscMessage("/bonetempo"); + message.add(bone); + message.add((float) tempo); + PApplet.println("trying to send to " + net.address() + " = " + tempo + " to bone " + bone); + oscP5.send(message, net); + } + +} diff --git a/src/animata/AnimataPlayback.java b/src/animata/AnimataPlayback.java index b1b942e..43d200d 100644 --- a/src/animata/AnimataPlayback.java +++ b/src/animata/AnimataPlayback.java @@ -33,9 +33,14 @@ public void panXBy(float delta) { x += delta; targetX += delta; } + public void panYBy(float delta) { + y += delta; + targetY += delta; + } } public static final float timeDivision = 42f; public static float gravity = 0; + private static boolean debug; private PApplet applet; private Layer root; private LayerView layersView; @@ -52,6 +57,7 @@ public AnimataPlayback(PApplet applet){ } private void setup(PApplet applet) { this.applet = applet; + Animator.init(applet); camera = new Camera(applet); this.applet.hint(PApplet.ENABLE_OPENGL_2X_SMOOTH); root = new Layer(); @@ -106,5 +112,15 @@ public void zoomCamera(float delta) { public void panCameraX(float delta) { camera.panXBy(delta); } + public void panCameraY(float delta){ + camera.panYBy(delta); + } + public void debug() { + debug = true; + + } + public static boolean debugging() { + return debug; + } } \ No newline at end of file diff --git a/src/animata/Animator.java b/src/animata/Animator.java new file mode 100644 index 0000000..6be94ad --- /dev/null +++ b/src/animata/Animator.java @@ -0,0 +1,89 @@ +package animata; + +import java.util.Observable; +import java.util.Observer; + +import processing.core.PApplet; + +public class Animator extends Observable implements Observer { + + + private static Engine engine; + float targetValue; + float initialValue; + float difference; + float frameIncrement; + public float currentValue = 0; + float frame = 0; + boolean isComplete = false; + + public Animator(float startValue, Observer target) { + engine.addObserver(this); + initialValue = currentValue = startValue; + addObserver(target); + } + + public void set(float value, int _frames) { + targetValue = value; + initialValue = currentValue; + difference = (value - initialValue); + frameIncrement = 1 / (float) _frames; + isComplete = false; + frame = 0; + frame += frameIncrement; + } + + public float getValue() { + return currentValue; + } + + public void update(Observable o, Object arg) { + // Just gonna assume it's always nextFrame. + if (frame >= 1 || frame == 0) return; + frame += frameIncrement; + currentValue = initialValue + (difference * easeOutQuad(frame)); + changed(); + if (frame >= 1) complete(); + } + + void changed() { + setChanged(); + notifyObservers(NEXT_FRAME); + } + + public boolean isComplete() { + return isComplete; + } + + public void complete() { + currentValue = targetValue; + isComplete = true; + notifyObservers("complete"); + } + + public void stop() { + // engine.deleteObserver(this); + } + + float easeOutQuad(float t) { + return -1 * (t /= 1) * (t - 2); + } + + public static void init(PApplet applet) { + engine = new Engine(applet); + + }; + public static final String NEXT_FRAME = "nextFrame"; + public static class Engine extends Observable{ + + public Engine(PApplet applet) { + applet.registerDraw(this); + } + public void draw() { + setChanged(); + notifyObservers(NEXT_FRAME); + } + + } + +} \ No newline at end of file diff --git a/src/animata/ControlFactory.java b/src/animata/ControlFactory.java index 3ae53aa..81bfdd6 100644 --- a/src/animata/ControlFactory.java +++ b/src/animata/ControlFactory.java @@ -2,8 +2,11 @@ import processing.xml.XMLElement; import rwmidi.MidiInput; +import animata.controls.BoneTempoKeyRanges; +import animata.controls.BoneTempoKeys; import animata.controls.Control; import animata.controls.NoteBone; +import animata.controls.NoteRangeBone; public class ControlFactory { @@ -12,9 +15,9 @@ public static Control createControl(XMLElement element, MidiInput in) { if(name.equals("notebone")) return new NoteBone(element, in); // if(name.equals("faderbone")) return new FaderBone(element); // if(name.equals("freqbone")) return new FreqBone(element); -// if(name.equals("noterangebone")) return new NoteRangeBone(element); -// if(name.equals("bonetempokeys")) return new BoneTempoKeys(element); -// if(name.equals("bonetempokeyranges")) return new BoneTempoKeyRanges(element); + if(name.equals("noterangebone")) return new NoteRangeBone(element,in); + if(name.equals("bonetempokeys")) return new BoneTempoKeys(element,in); + if(name.equals("bonetempokeyranges")) return new BoneTempoKeyRanges(element, in); return new Control(element, in); diff --git a/src/animata/Controller.java b/src/animata/Controller.java index 4e7ee00..a08395f 100644 --- a/src/animata/Controller.java +++ b/src/animata/Controller.java @@ -43,6 +43,8 @@ public boolean setBoneTempo(String name, Float tempo) { for (Bone bone : bones) { bone.setTempo(tempo); } + // TODO: extract interface for Animata calls + Animata.setBoneTempo(name, tempo); return true; } @@ -51,6 +53,8 @@ public boolean animateBone(String name, float scale) { for (Bone bone : bones) { bone.setScale(scale); } + // TODO: extract interface for Animata calls + Animata.setBone(name, scale); return true; } diff --git a/src/animata/LayerView.java b/src/animata/LayerView.java index 3e8fe7d..2e1b30f 100644 --- a/src/animata/LayerView.java +++ b/src/animata/LayerView.java @@ -4,6 +4,8 @@ import processing.core.PApplet; import animata.model.Layer; +import animata.model.Skeleton.Bone; +import animata.model.Skeleton.Joint; public class LayerView extends ViewBase { @@ -30,21 +32,32 @@ public void draw() { applet.pushMatrix(); doTransformation(); applet.pushMatrix(); - drawMesh(); + if(mesh!= null) mesh.draw(); + if(AnimataPlayback.debugging()) drawDebugStuff(); drawChildLayers(); applet.popMatrix(); applet.popMatrix(); } + private void drawDebugStuff() { + if(layer.skeleton == null) return; + for(Joint joint : layer.skeleton.joints){ + applet.fill(0xFFFFFF00); + applet.ellipse(joint.x, joint.y, 5, 5); + } + applet.stroke(0x99FF00FF); + applet.strokeWeight(5); + for(Bone bone : layer.skeleton.bones){ + applet.line(bone.j1.x, bone.j1.y, bone.j0.x, bone.j0.y); + } + } + private void drawChildLayers() { for (LayerView layerView : layers) { layerView.draw(); } } - private void drawMesh() { - if(mesh!= null) mesh.draw(); - } // this is like the calcTransformationMatrix method from the original, but called during draw private void doTransformation() { diff --git a/src/animata/MeshView.java b/src/animata/MeshView.java index 1996630..27f8639 100644 --- a/src/animata/MeshView.java +++ b/src/animata/MeshView.java @@ -16,6 +16,7 @@ public MeshView(PApplet applet, Layer layer) { this.layer = layer; } public void draw() { + applet.noStroke(); drawFaces(layer.mesh.faces); // applet.textFont(font); // applet.fill(0); diff --git a/src/animata/controls/BoneTempoKeyRanges.java b/src/animata/controls/BoneTempoKeyRanges.java new file mode 100644 index 0000000..3ae6999 --- /dev/null +++ b/src/animata/controls/BoneTempoKeyRanges.java @@ -0,0 +1,39 @@ +package animata.controls; + +import processing.xml.XMLElement; +import rwmidi.MidiInput; +import animata.NoteParser; +import animata.NoteParser.BadNoteFormatException; + +public class BoneTempoKeyRanges extends Control { + private Integer low; + private Integer high; + private int bonecount; + private float tempo; + private BoneTempoKeys[] ranges; + private String boneRoot; + public BoneTempoKeyRanges(XMLElement element, MidiInput in) { + super(element, in); + try { + low = NoteParser.getNote(element.getStringAttribute("low", "1")); + high = NoteParser.getNote(element.getStringAttribute("high", "100")); + } catch (BadNoteFormatException e) { + System.out.println(e.getMessage()); + } + channel = element.getIntAttribute("channel", 16) - 1; + boneRoot = element.getStringAttribute("bone"); + tempo = element.getFloatAttribute("tempo", 1); + bonecount = element.getIntAttribute("bonecount",1); + addRanges(); + } + + private void addRanges() { + ranges = new BoneTempoKeys[bonecount]; + float step = (((float)high)-((float)low))/((float)bonecount); + for (int i = 0; i < bonecount; i++) { + int rangeLow = low + (int)(i*step); + System.out.println("added range low=" + rangeLow + " step was " + step); + ranges[i] = new BoneTempoKeys(in, rangeLow, rangeLow + (int)step, boneRoot+i, tempo, channel); + } + } +} diff --git a/src/animata/controls/BoneTempoKeys.java b/src/animata/controls/BoneTempoKeys.java new file mode 100644 index 0000000..1445c6a --- /dev/null +++ b/src/animata/controls/BoneTempoKeys.java @@ -0,0 +1,67 @@ +package animata.controls; + +import processing.xml.XMLElement; +import rwmidi.MidiInput; +import rwmidi.Note; +import animata.Controller; +import animata.NoteParser; +import animata.NoteParser.BadNoteFormatException; + +public class BoneTempoKeys extends Control { + + private Float tempo; + private String bone; + private int keysPressed = 0; + private int low; + private int high; + + public BoneTempoKeys(MidiInput in, int low, int high, String bone, float tempo, int channel) { + super(channel, in); + this.low = low; + this.high = high; + this.bone = bone; + this.tempo = tempo; + } + + public BoneTempoKeys(XMLElement element, MidiInput in) { + super(element, in); + try { + low = NoteParser.getNote(element.getStringAttribute("low", "1")); + high = NoteParser.getNote(element.getStringAttribute("high", "100")); + } catch (BadNoteFormatException e) { + System.out.println(e.getMessage()); + } + bone = element.getStringAttribute("bone"); + tempo = element.getFloatAttribute("tempo"); + Controller.getInstance().setBoneTempo(bone, 0f); + } + + public void noteOnReceived(Note n) { + if(n.getChannel()!= channel) return; + int pitch = n.getPitch(); + if (pitch < low) return; + if (pitch > high) return; + float trigger = 0f; + Float newTempo = 1f; + keysPressed++; + newTempo = tempo; + + Controller.getInstance().setBoneTempo(bone, newTempo); + // used for piano + Controller.getInstance().animateBone("trigger" + bone, trigger); + } + + public void noteOffReceived(Note n) { + if(n.getChannel()!= channel) return; + int pitch = n.getPitch(); + if (pitch < low) return; + if (pitch > high) return; + float trigger = 1f; + keysPressed--; + float newTempo = 1f; + if (keysPressed == 0) newTempo = 0f; + Controller.getInstance().setBoneTempo(bone, newTempo); + // used for piano + Controller.getInstance().animateBone("trigger" + bone, trigger); + } +} diff --git a/src/animata/controls/Control.java b/src/animata/controls/Control.java index 5bc5a3a..4b674b8 100644 --- a/src/animata/controls/Control.java +++ b/src/animata/controls/Control.java @@ -8,9 +8,18 @@ public class Control { protected int channel; protected final Controller controller = Controller.getInstance(); + protected MidiInput in; public Control(XMLElement element, MidiInput in) { + init(element.getIntAttribute("channel", element.getParent().getIntAttribute("channel")) - 1,in); + } + public Control(int channel, MidiInput in){ + init(channel, in); + } + private void init(int channel, MidiInput in) { if (in == null) System.out.println("error, MIDI Input Device not supplied!"); - channel = element.getIntAttribute("channel", element.getParent().getIntAttribute("channel")) - 1; + this.channel = channel; + this.in = in; + in.plug(this); } } diff --git a/src/animata/controls/NoteBone.java b/src/animata/controls/NoteBone.java index 03e3e52..4d63b24 100644 --- a/src/animata/controls/NoteBone.java +++ b/src/animata/controls/NoteBone.java @@ -1,18 +1,23 @@ package animata.controls; +import java.util.Observable; +import java.util.Observer; + import processing.xml.XMLElement; import rwmidi.MidiInput; import rwmidi.Note; +import animata.Animator; import animata.NoteParser; import animata.NoteParser.BadNoteFormatException; -public class NoteBone extends Control { +public class NoteBone extends Control implements Observer { private String bone; private Integer note; private float on; private float off; + private Animator animator; public NoteBone(XMLElement element, MidiInput in) { super(element, in); @@ -24,19 +29,21 @@ public NoteBone(XMLElement element, MidiInput in) { } catch (BadNoteFormatException e) { e.printStackTrace(); } - - in.plug(this); + animator = new animata.Animator(off,this); System.out.println("Created notebone for bone " + bone + " note=" +note); } public void noteOnReceived(Note n){ if(n.getChannel() != channel) return; if(n.getPitch() != note) return; - controller.animateBone(bone, on); + animator.set(on,3); } public void noteOffReceived(Note n){ if(n.getChannel() != channel) return; if(n.getPitch() != note) return; - controller.animateBone(bone, off); + animator.set(off,10); + } + public void update(Observable o, Object arg) { + controller.animateBone(bone, animator.currentValue); } } diff --git a/src/animata/controls/NoteRangeBone.java b/src/animata/controls/NoteRangeBone.java new file mode 100644 index 0000000..e21981f --- /dev/null +++ b/src/animata/controls/NoteRangeBone.java @@ -0,0 +1,36 @@ +package animata.controls; + +import animata.Controller; +import animata.NoteParser; +import animata.NoteParser.BadNoteFormatException; +import processing.xml.XMLElement; +import rwmidi.MidiInput; +import rwmidi.Note; + +public class NoteRangeBone extends Control { + + private String bone; + private float range; + private int low; + private int high; + + public NoteRangeBone(XMLElement element, MidiInput in) { + super(element, in); + try { + low = NoteParser.getNote(element.getStringAttribute("low", "1")); + high = NoteParser.getNote(element.getStringAttribute("high", "100")); + } catch (BadNoteFormatException e) { + System.out.println(e.getMessage()); + } + bone = element.getStringAttribute("bone"); + range = (float) high - low; + } + public void noteOnReceived(Note n){ + if(n.getChannel() != channel) return; + int pitch = n.getPitch(); + if(pitch < low ) return; + if(pitch > high) return; + float length = 1f - ((float)((pitch - low)) / range); + Controller.getInstance().animateBone(bone, length); + } +} diff --git a/src/animata/model/Skeleton.java b/src/animata/model/Skeleton.java index e17c035..d5f94c7 100644 --- a/src/animata/model/Skeleton.java +++ b/src/animata/model/Skeleton.java @@ -46,7 +46,7 @@ private void setInitialConditions() { dst = vd; - float vdnorm = vd / (bone.attachRadiusMult * bone.dOrig * .5f); + float vdnorm = vd / (bone.radius * bone.size * .5f); if (vdnorm >= 1) { @@ -74,23 +74,18 @@ private void assignAttributes(XMLElement element) { public class Bone { - private static final float BONE_DEFAULT_DAMP = 0.5f; - private Joint j0; - private Joint j1; - private float stiffness; + public Joint j0; + public Joint j1; private float scale; private float maxScale; private float minScale; public Float tempo; - private float radius; - private boolean selected; - private float size; private float time; private AttachedVertex[] attachedVertices; private String name; - private float damp; - public float dOrig; - private float attachRadiusMult; + private float stiffness; + public float size; + private float radius; private float falloff; public Bone(XMLElement element) { @@ -102,17 +97,6 @@ public Bone(XMLElement element) { } private void setInitialConditions() { - damp = BONE_DEFAULT_DAMP; - - float x0 = j0.x; - float y0 = j0.y; - float x1 = j1.x; - float y1 = j1.y; - - dOrig = PApplet.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); - - - attachRadiusMult = 1.0f; falloff = 1.0f; } @@ -128,7 +112,6 @@ private void assignAttributes(XMLElement element) { tempo = element.getFloatAttribute("tempo"); time = element.getFloatAttribute("time"); size = element.getFloatAttribute("size"); - selected = element.getIntAttribute("selected") == 1; radius = element.getFloatAttribute("radius"); } @@ -148,23 +131,15 @@ public void simulate() { time += tempo / AnimataPlayback.timeDivision; // FIXME animateScale(0.5f + PApplet.sin(time) * 0.5f); } - - float x0 = j0.x; - float y0 = j0.y; - float x1 = j1.x; - float y1 = j1.y; - - float dx = (x1 - x0); - float dy = (y1 - y0); + float dx = (j1.x - j0.x); + float dy = (j1.y - j0.y); float dCurrent = PApplet.sqrt(dx*dx + dy*dy); -// if (dCurrent > FLT_EPSILON) -// { - dx /= dCurrent; - dy /= dCurrent; -// } + dx /= dCurrent; + dy /= dCurrent; + - float m = ((dOrig * scale) - dCurrent) * damp; + float m = ((size * scale) - dCurrent) * stiffness; if (!j0.fixed ) { @@ -234,11 +209,11 @@ public void setScale(Float value) { public class Joint { - private float x; - private float y; - private boolean fixed; + public float x; + public float y; + public boolean fixed; private boolean selected; - private String name; + public String name; public Joint(XMLElement element) { name = element.getStringAttribute("name",""); @@ -256,8 +231,8 @@ public void simulate() { } - private Joint[] joints; - private Bone[] bones; + public Joint[] joints; + public Bone[] bones; private final Mesh mesh; private static ArrayList allBones = new ArrayList(); diff --git a/src/test/TestScene.java b/src/test/TestScene.java new file mode 100644 index 0000000..7d28365 --- /dev/null +++ b/src/test/TestScene.java @@ -0,0 +1,41 @@ +package test; + +import microkontrol.MicroKontrol; +import processing.core.PApplet; +import animata.Animata; +import animata.AnimataPlayback; + +public class TestScene extends PApplet { + private AnimataPlayback playback; + private MicroKontrol mk; + + public void setup() { + size(950, 614, OPENGL); + Animata.startOSC(this); + playback = new AnimataPlayback(this); + + playback.loadSet("set.xml"); + playback.debug(); + MicroKontrol.init(this); + mk = MicroKontrol.getInstance(); + } + + public void draw() { + if (keyPressed) { + if (keyCode == DOWN) playback.panCameraY(10); + if (keyCode == UP) playback.panCameraY(-10); + } + playback.panCameraX(mk.joystick.getX() * 30); + playback.zoomCamera(mk.joystick.getY() * 30); + background(255); + + playback.draw(); + + } + + public static void main(String[] args) { + PApplet.main(new String[] { "--bgcolor=#c0c0c0", "TestScene" }); + + } + +}