All posts by werner

Java join() slow

And there I was thinking that Java would offer an efficient implementation of join on threads. One that would be triggered when a thread signals it had come to an end.

Well… not so. join(0), which means, wait indefintely for the thread, results in a completely braindead while(isAlive()) wait() loop.

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

To make this work as it should we had to wait for a threadcounter to reach 0 and let each thread lower the threadcounter and call notifyAll. A very tedious process. Why do we have a join if it doesn’t work in a performant manner ?

Latency calculations for Zathras

Because Zathras-1 now has to interact with linearly interpolated timestretches, we had to calculate the latency of our timestretcher.

Multiple results were new to me and interesting to see.

  1. An sliding window fourier transform has a latency of 0. I did not expect that at all.
  2. When an input frame is stretched, then each sample of the inputframe will have  a different latency.
  3. The latencies of all samples, except the middle one, depend on the playback speed. Yes, you read that right: the playback speed affects the latency. And that is really something we do not want to compensate for. Therefore, use as reference the middle of the frame.
  4. It is possible to have negative latencies. Part of the signal is already out before we believe we provided it to the timestretcher.

http://werner.yellowcouch.org/Papers/zathras15/#toc23

JavaFx: A first look

Porting BpmDj to Desktop required us to think about what platform to use. We settled on JavaFx because it has 3D support. Not that we needed that immediatelly, but it is something we might want to add to BpmDj somewhere in the far future.

At first sight JavaFx is fairly solid. The properties, their getters and setters are really well defined. Even listeners to collections are well defined. That aspect I like a lot, especially since I have been creating a similar system myself. Both serve the same purpose and seem to have come up with the same solutions. Basicaly: they did it like I would have done it.

JavaFx CSS

The rendertree is quite professional, and styleable through CSS. Yet, the CSS properties are badly defined. More than once I had to dump the scene tree in order to figure out what styles are used at what point. Below is a routine that does exactly that.

public static void dump(Node n) { dump(n, 0); }
private static void dump(Node n, int depth) {
    for (int i = 0; i < depth; i++) System.out.print("  ");
    System.out.print(n);
    for(PseudoClass pc :n.getPseudoClassStates())
        System.out.print(" "+pc);
    System.out.println();
    if (n instanceof Parent)
        for (Node c : ((Parent) n).getChildrenUnmodifiable())
            dump(c, depth + 1);
}

The order in which rules are processed is counter-intuitive for the programmer. From the docs:  a style from a user agent style sheet has lower priority than a value set from code, which has lower priority than a Scene or Parent style sheet

This means that, allthough you might have written a Tag class, extending the Label class, which changes the color of a specific entry through setBackground, it will simply have no effect. This leads to quite a lot of confusion online.  On stackoverflow there are hordes of people asking ‘how can I change this style from within my program, without using CSS’. The answers might or might not work, it all depends on whether the original poster had a stylesheet assigned to its scene or not. But even then, programatorically removing a label its styleclass (and replacing it with our own),  didn’t help either because the class still ‘inherited’ scene-style properties from its container.  And that might include an * selector.

Basically CSS is a total mess. The JavaFX Implementation makes some good choices. Well… the best given the circumstances. And of course, it is easy to criticise this, but in all honosty I would not be able to suggest a better alternative to resolve such ambiguity.  Well,  maybe I can: programmer trumps CSS. That is, any style set from within the program should be honored.

Jargon

Graphs – In JavaFx jargon there is a scene graph. Mark the word graph. You would expect that one node could potentially appear at multiple places in the scene. The documentation however scales the idea of a graph somewhat down to: ‘A scene graph is a set of tree data structures where every item has zero or one parent, and each item is either a “leaf” with zero sub-items or a “branch” with zero or mor sub-items. ‘ Why don’t they call it a tree then ?

Bias – Another occasion where this happens is with the word ‘content-bias’. What the hell dudes ? If you wonder what this is: it is the orientation of the layout: horizontal or vertical ? Why not call it orientation or direction ? Because they want to feel important ?

Insets – Another nice example of this is the word ‘insets’, which stands for ‘inside offsets’. So if I want padding to a control, I have to set the insets. And it goes on like this: lots of words that are either too broad, or don’t mean what they really mean. With respect to the insets, it is even more confusing because, depending on the component type, you get -fx-border-insets, margins, padding, background-insets and -opaque-insets. I don’t mind to have the ability to tune things, but at least document them in a nice drawing what they all mean.

Percentage – a value 0.5 is a percentage 50%. So if I say: express me something as a percentage then you get values between 0 and 100. Yet, in JavaFx there seems to be some confusion on when the value ranges to 1 and when to 100. So, when you then read the documentation for the BackgroundSize constructor, with a parameter documentation as widthAsPercentage the width is to be interpreted as a percentage; then you would expect the width to be in 0..100 range. Well. No, it is between 0 and 1.

Managed – if you want to decide on the layoutparameters of a node yourself, and thus manage the node you have to set this to ‘off’.  Or bascially, you have to tell the tree that it should not be managing the node. This is a standard input-output confusion, and something that should be avoided in code.

Smooth or non-smooth ?

That is a question, and one they apparently never answered. At various places there are markers to set things smooth, or not. Yet, the render engine might or might not take this into account. To illustrate this. The beatgraphs must be aliased. We want every bar clearly marked, so we do not want smoothing when we draw the beatgraphs. Thus, by creating an Image, which is marked as ‘non-smooth’, and then drawing it onto the GraphicsContext of a canvas… results indeed in a smoothened version (go figure). Then, at other places we actually want the render engine to smoothen something. E.g: when we draw the logo in the about box, we need to downscale a fairly large image to a smaller size. Whatever collection of smooth options we set, the resulting image was never properly smoothed. To that end we actually had to write our own routine. Then there is the problem that Images have a smooth flag, which can only be set if the image is loaded from an Inputstream of sorts. When the Image is created through a WritableImage, then the smooth-flag is always off; without a possibility to actually turn it on. However, don’t mind the smooth flag of the Image. The ImageView has one of its own (why two times the same flag if it is ignored anyway). I probably can go on about this aspect of the engine. Suffice to say that this is extremely badly defined, and the rendering is platform dependent, which makes it a useles flag.

Menubars & Stages

The menubar – apparently a total mess to get that working correctly.  In particular the menubar on icewm has submenus that overlay the menubar itself because javafx substracts the window decoration from the height. More so, the submenu cannot be selected as long as the menubar underneath is still selected. On dual screens: if the application is on the wrong screen, menus simply don’t open.

Which screen ? On linux the interaction with the windowmanager causes even more  problems. When I open the application in fullscreen (through the windowmanager), and then the app opens a dialogbox, suddenly the entire app jumps to the other screen, while the dialog remains at the first screen. It is really quit wacko how javafx is not able to figure out where the screen to use, how large window decorations are and  how to deal with that properly.

Layout

Layout – refers to the problem of deciding where to place what on the screen. This is well defined throughout the tree. The inner loop is really a charming piece of software (no irony intended). When a node says it needs a new layout, that message propagates upward in the tree, marking the parent nodes with a ‘branch_dirty’, thereby in the next layout cycle, the various branches that required a new layout are processed quite efficiently.  So that part I like a lot.

Inconsistent interface – The thing I don’t like is that certain properties are shadowed or not part of a node as such. A node in itself has neither a minimum/preferred nor a maximum width. Yet it does have a width. Yet, changing the width of a node depends on the context you use it in. In some classe you have to call setFitWidth, in others you have to bend yourself in corners to actually get the thing  resized (see below), at other places, setting the width with setWidth, will not do a thing, there you need to use setPrefWidth. And so on. It is always interesting to try to figure out how you will actually get a node to size correctly.

Non resizable canvases – Then there is the trick with the non-resizable canvas.  If you dig into the code you find that the class hierarchy contains multiple ‘height’ properties at different levels of the tree; all meaning slightly different things. You also figure out that the ‘resize’ operation of a node is simply not implemented, and will not resize a thing. It is quite remarkable how difficult it is to get a Canvas to rescale properly.

This even extends to the problem of figuring out what the width of a node then actually is. In control nodes (most stylable nodes inherit from this one, except video, image and canvases), you cannot ask the heightProperty to tell you something useful, certainly not when you are drawing the nodes content. There you have to use getWidth, but not if you are in a Canvas, then you cannot simply use that because the width shadows the width of the node. At other places you need to ask for the preferredWidth (prefWidth), which will tell you something more useful.

Porbably, the reason the interface is so inconsistent lies in the growth of JavaFx. Initially, a node only needs to know its size. It does not need any information about its minimum/preferred/maximum sizes. That was left for more advance layout approaches. So, a new layer was slapped on it, with a similar interface, but this time it is presented as high level (bean) properties.  Yet that is too slow, so a cache layer was added to it. Again with similar names. In the end, as an outsider, it is kinda hard to figure out what actually is going on. And even the theoretical possibilities often do not work out.

The always growing layout – In JavaFx there is a mismatch between what most users think would happen and what the program actually does. Nobody even bothers anymore to figure out what the rules now actually are. This is in other words a failure of the software design and is very reminiscent of the ‘good old AWT classes’. If you cannot communicate clearly how it is supposed to work, then how do you expect people to make the fullest use of it ?

One good  example of this is the always growing layout.  The layout mechanism has the possibility to bind the width of 1 element to the width of it sparent. However, often this leads to a loop in which the dialog keeps on growing. Neverthless, on stackoverflow you have often people simply stating: ‘just bind it like this’. They should really try that ‘solution’ in real life applications. Most of the time it will not do what you want.

Rendering

Performance – The render engine – a very nice step back from object orientation. Objects are used to decide what to render when, yet the render engine itself is dissociated from it. It merely receives a set of commands that it will execute. On 3D accelerated cards this goes very fast. This aspect is worth applauding, allthough not uncommon in the industry. Android, Qt, all went this direction. Yet, in many frameworks, it is difficult to draw things sufficiently fast. Also JavaFx has problem with this.  A special purpose GraphicalContext, which functions as a canvas/bitmap that can be used to create a texture before it goes to the underlying engine.  Typically operations on such bitmaps are slow and the resulting framerate drops dramatically when even a moderate amount of objects are drawn. Compared to android, the JavaFx direct draw pipeline is very slow. When only using image-based components, the performance is acceptable.

Repaints are automatic, in theory – After layout, the content of nodes nmust be rendered. That is a tricky operation. Most routines are somewhere in native code and if you want to draw your own image you might need to use a Canvas. Fair enough, I already talked about this.

However, sometimes an update to a node does not trigger a render cycle. Particularly listviews and their listcels are susceptable to this. And there is no sensible way to demand a repaint. People have been using a trick to force things to render: ask the window to make a snapshot of its content as a 1×1 pixel. That triggers the dirty render flags of all nodes. Honostly: is that the best we can do in 2016 ? Take a screenshot of the entire app so that it will actually render ? Interestingly, that routine itself eventually states

// we need to mark the entire scene as dirty
// because dirty logic is buggy
if (scene != null && scene.impl_peer != null)
    scene.setNeedsRepaint();

Thus instead of solving the underlying problem, the developers worked around it. That does not give me a warm fuzzy feeling.

Did this cause problems ? Of course, otherwise I would not have noticed. In practice,  listview cel updates would effectively screw up an entire path to the root of update requests. Not only was the listcel not updated, its parents weren’t either. To solve that we had to write code that would actually ‘change’ the content of a textfield, before setting it to what we wanted. So in the end you get code like

String currentText=tempo.getText();
if (Objects.equal(currentText,info.tempo))
  {
  if (currentText==null)
    tempo.setText("bla bla bla");
   else
    tempo.setText(null);
   }
tempo.setText(info.tempo);

Yes that was ‘entertaining’, and totally useles. The back and forth was necessary to actually get JavaFx render our cells.

Sometimes things just refuse to render – to prohibit a node from drawing outside its area, in particular our beatgraph scroller, we have to set a clipping mask. That is done as follows:

a.beginPath();
a.rect(0, 0, getWidth(), beatgraphHeight);
a.closePath();
a.clip();

Ocasionaly this would not work and no rendering would take place at all. After many hours of digging through code and testing we found that when the beatgraphHeight was a non-integer value, the clip was simply not performed on the Graphics2DContext. What we had to do to get this working is write

a.rect(0, 0, getWidth(), Math.round(beatgraphHeight));

Whether this was solely related to my linux install or not I couldn’t tell. It was nonetheless a bug that I would typically associate with AWT.

When I followed the internals of the rectangle call you see that it transforms all double coordinates to float, which is another issue one could have with JavaFx. Internally it is all float, yet the API presents everything as doubles. It would be a lot faster to keep everything in the same bitlevel. Not only that you would not have typical java-style float to double convertion errors.

Internal images too large – when we drew the beatgraph overview at the top of the app, everything worked fine… Until we added

g.setEffect(prelistening ? gray : null);

In order to render this effect, even the parts it would not render in the end, it sometimes had to allocate an image larger than 16384 pixels. That was because we drew a line at x-position 5000 position, yet because that position was not visible anyway, it didn’t matter, we thought.
Nevertheless, the effect rendering demanded an internal image large enough to store the full range of what we were drawing, to then make everything grey. From a general purpose point of view, this is somewhat expected bcause pixels outside the visible region could potentially affect pixels within the visible region. Yet, it would be useful if they would make some sensible estimates that reflect the reality of a color transformation (that is: stick to the sizes you got). Even in case of blur filters, adding a margin that reflects the size of the blur radius would suffice to properly render the visible region. Also, in this case it was nearly impossible to understand what went on becaue the rendering takes place in its own thread. First we tried to figure out where we allocated such large image; couldn’t find any. Then we tried to set breakpoints; and finally we were lucky that this part of the rendering was actually allocated in non-native code; so we could understand what went on.

Object orientation ?

Internal Datastructures – It is here that I loose my respect for the developers. The thing with data structures is the following: how many times you hear people say: don’t use arrays (even for sensible purposes),  always use ADT’s such as ArrayList, Tree etc.. Yet… time and time again I find that the java developers hate their own data structures. In 1998 I figured that their runtime stack was not really a stack but an array that was copied when it grew too large. The result: if the stack became larger, the program became slower. Now, in JavaFx, exactly the same problem. If the eventqueue of javafx becomes larger, then the program slows down. This makes no sense. At all.

And then there is the problem of the selectionmodel that is simply stored as a straight linked list, without any option to quickly find an element. Of course, when you add something to the list, the listview will automatically try to find the current selection back in the list. And here it is that the performance penalty really hits. If you batch insert 27000 elements, even then for each of those elements, the selection is recovered, meaning that we have an O(n²) performance. That is really bad, because a HashMap would already have helped with finding the currently selected items; or when a batch insert takes place, only recover the current selection after really inserting all elements. In our case, having 27000 songs in a list, with 1 song selected would lead to batch insertion times of about 5 minutes. Yes that is no laughing matter. That is a fatal performance penalty.

Because of such crappy datastructures, you often have to twist the surrounding program in corners. In the end, after we understood what went on, we removed the selection, updated the list, and then recovered the selection ourselves.

The case of the swallowed exceptions – Sometimes you just get an ‘Exception in Application start’. Which is actually not true. It is not in the application start that it takes place. If it were you would be able to write a catch in that start method. No it is an exception somewhere in the event queue handling while the start method has not yet finished (which is pretty much the entire program). To debug that, the easiest way is to go to LauncherImpl and set a breakpoint at the line that tells you there is an exception. Then debug it and when it crashes you can actually see the exception. And hope the debugger is smart enough to find the exception back in the compiled code.

Swallowing exceptions just like that is not only bad framework design, it is an absolute horrible thing to do to users of your framework. Now they can really go onto an egghunt to find that non specicifc ‘Exception in Application start’.

The singular JavaFx Application – only one JavaFx application  can be launched per virtual machine. That’s annoying and breaks any decent OO design. It introduces a global state that really not necessary. It was for instance impossible to create a loader that would download a bpmdj.jar and then jump into the JavaFx launch of the app. Jumping into that launch was not possible because the loader had already instantiated its JavaFX application. The easiest way to solve this was actually bypass the entire LaunchImpl class. Thereby we got rid of the swallowed exceptions and we were able to open multiple application stages without any problem.

One or more listeners – In JavaFx there is a certain ambiguity between having properties that can have multiple listeners and events to which only one listener can be attached. An example: attaching multipled listeners to the hiding of a stage is not possible, which is a bit weird because changes to the width can have multiple listeners.

Things I miss from Android

Event time information – Q: when was an event placed in the queue ? A: Impossible to know And that is annoying if you write a real time application that must know when a key was pressed or a mouse was clicked.

A timed runLater – sometimes you want things to be done a bit later. I find it a waste of resources to spawn a thread for that, have that thread wait 3 seonds and then post a runLater message to the event queue to actually do its work. It would be a lot easier if there would be a Platform.runLater(<runnable>, <delay>) call.

Conclusion

From what I’ve seen the javafx framework is a better designed framework in an early stage. Compared to the AWT many painful hooks have been removed and from various design choices, often the most powerful with the least amount of code has been choosen.

A good example of this are the clipping regions. In JavaFx, each node can be clipped, but by default isn’t. This is useful when nodes animate beyond their parents boundaries. Yet, initially you have the feeling that you do nothing else but set clips. Until you realize that it has the advantage that you can set one clip for an entire branch without requiring each branch to do its own clipping. Efficiency & flexibility in one neat package. And that bascially summarizes JavaFx.

Sometimes the OO design is broken; and I hope this will not become a trend.  At other places we noticed standard growing pains of a platform that is being developed.

 

 

 

 

 

 

 

 

 

Resizable JavaFx Canvas

To make a JavaFx canvas resizable all that needs to be done is override the min/pref/max methods. Make it resizable and implement the resize method.

With this method no width/height listeners are necessary to trigger a redraw. It is also no longer necessary to bind the size of the width and height to the container.

public class ResizableCanvas extends Canvas {

    @Override
    public double minHeight(double width)
    {
        return 64;
    }

    @Override
    public double maxHeight(double width)
    {
        return 1000;
    }

    @Override
    public double prefHeight(double width)
    {
        return minHeight(width);
    }

    @Override
    public double minWidth(double height)
    {
        return 0;
    }

    @Override
    public double maxWidth(double height)
    {
        return 10000;
    }

    @Override
    public boolean isResizable()
    {
        return true;
    }

    @Override
    public void resize(double width, double height)
    {
        super.setWidth(width);
        super.setHeight(height);
        paint();
    }

Note that the resize method cannot simply call Node.resize(width,height), because the standard implementation is effectivele empty.

Is Wikipedia sustainable ?

As with every year: the wikimedia foundation is begging for money again. So I thought: let’s have a look at how my money would be spend.

About 1/4th of the money would be spend on administration. Not that I mind some administration but that is excessive. Let’s say they get around 20’000’000 EUR in donations.  5’000’000 EUR for administration of wikipedia is too much. The box ‘fundraising’ of course says that some fundraisers get their money back by being hired by wikimedia. In that case their ‘donation’ is no donation and should not be listed as such.

Annual report assets

A further graphic of the year 2014 shows that less than 40% of the money would be spent on actual product improvement. Around 60% goes to administration of sorts.

Annual report assets

If you are someone living in germany and you wonder how your donation will benefit the locality: it won’t do very much. Your donation is largely funneled into the US. And even less of that money is spend on product improvement: 11%.

WMDE-Mittelverwendung-2016

With the current set of employees and wikipedias consistent hunger for money, the natural question to ask is: what happens when people are tired of donating to wikipedia ? Is wikipedia sustainable ?

Will all the content be ripped from the net, slowly decay into irrelevance, filled with more spam, less content ?

I guess the only way to make wikipedia truly sustainable is by making it peer to peer. Storing all articles in a completely peer to peer manner.

And as expected if you only want to support bureacracy. Two years later: https://www.theregister.co.uk/2017/06/07/golden_handshakes_at_wikipedia/

Brainwaves note combinations

Below is a table showing which notes generate what brainwave oscilations. The idea is that each note is played in a separate channel.

Brainwave 1.9446332095341745 C1 and C#1
Brainwave 2.060267117566937 C#1 and D1
Brainwave 2.1827769755841757 D1 and D#1
Brainwave 2.3125716488486177 D#1 and E1
Brainwave 2.4500843150167455 E1 and F1
Brainwave 2.5957739098288073 F1 and F#1
Brainwave 2.7501266587643656 F#1 and G1
Brainwave 2.9136576997744896 G1 and G#1
Brainwave 3.086912802506866 G#1 and A1
Brainwave 3.2704701897611983 A1 and A#1
Brainwave 3.464942467254282 A#1 and B1
Brainwave 3.670978668134147 B1 and C2
Brainwave 3.8892664190683774 C2 and C#2
Brainwave 4.004900327101112 C1 and D1
Brainwave 4.1205342351338885 C#2 and D2
Brainwave 4.243044093151113 C#1 and D#1
Brainwave 4.365553951168337 D2 and D#2
Brainwave 4.495348624432793 D1 and E1
Brainwave 4.625143297697278 D#2 and E2
Brainwave 4.762655963865363 D#1 and F1
Brainwave 4.90016863003342 E2 and F2
Brainwave 5.045858224845553 E1 and F#1
Brainwave 5.191547819657643 F2 and F#2
Brainwave 5.345900568593173 F1 and G1
Brainwave 5.500253317528745 F#2 and G2
Brainwave 5.663784358538855 F#1 and G#1
Brainwave 5.827315399548979 G2 and G#2
Brainwave 6.000570502281356 G1 and A1
Brainwave 6.173825605013732 G#2 and A2
Brainwave 6.187677302685287 C1 and D#1
Brainwave 6.3573829922680645 G#1 and A#1
Brainwave 6.540940379522397 A2 and A#2
Brainwave 6.5556157419997305 C#1 and E1
Brainwave 6.73541265701548 A1 and B1
Brainwave 6.929884934508564 A#2 and B2
Brainwave 6.945432939449539 D1 and F1
Brainwave 7.135921135388429 A#1 and C2
Brainwave 7.341957336268308 B2 and C3
Brainwave 7.3584298736941705 D#1 and F#1
Brainwave 7.560245087202524 B1 and C#2
Brainwave 7.778532838136755 C3 and C#3
Brainwave 7.795984883609918 E1 and G1
Brainwave 8.009800654202266 C2 and D2
Brainwave 8.241068470267749 C#3 and D3
Brainwave 8.259558268367662 F1 and G#1
Brainwave 8.486088186302226 C#2 and D#2
Brainwave 8.500248951533905 C1 and E1
Brainwave 8.731107902336703 D3 and D#3
Brainwave 8.750697161045721 F#1 and A1
Brainwave 8.990697248865615 D2 and E2
Brainwave 9.005700057016476 C#1 and F1
Brainwave 9.250286595394527 D#3 and E3
Brainwave 9.271040692042554 G1 and A#1
Brainwave 9.525311927730698 D#2 and F2
Brainwave 9.541206849278346 D1 and F#1
Brainwave 9.800337260066868 E3 and F3
Brainwave 9.822325459522347 G#1 and B1
Brainwave 10.091716449691063 E2 and F#2
Brainwave 10.108556532458536 D#1 and G1
Brainwave 10.383095639315286 F3 and F#3
Brainwave 10.406391325149627 A1 and C2
Brainwave 10.691801137186388 F2 and G2
Brainwave 10.709642583384408 E1 and G#1
Brainwave 10.95033326655065 C1 and F1
Brainwave 11.000506635057462 F#3 and G3
Brainwave 11.025187554456807 A#1 and C#2
Brainwave 11.327568717077725 F#2 and G#2
Brainwave 11.346471070874529 F1 and A1
Brainwave 11.601473966845283 C#1 and F#1
Brainwave 11.654630799097788 G3 and G#3
Brainwave 11.680779322336413 B1 and D2
Brainwave 12.001141004562712 G2 and A2
Brainwave 12.02116735080692 F#1 and A#1
Brainwave 12.291333508042712 D1 and G1
Brainwave 12.347651210027436 G#3 and A3
Brainwave 12.375354605370603 C2 and D#2
Brainwave 12.714765984536129 G#2 and A#2
Brainwave 12.735983159296836 G1 and B1
Brainwave 13.022214232233026 D#1 and G#1
Brainwave 13.081880759044992 A3 and A#3
Brainwave 13.111231483999504 C#2 and E2
Brainwave 13.47082531403096 A2 and B2
Brainwave 13.493304127656494 G#1 and C2
Brainwave 13.546107176379458 C1 and F#1
Brainwave 13.796555385891274 E1 and A1
Brainwave 13.859769869017128 A#3 and B3
Brainwave 13.890865878899035 D2 and F2