Friday, September 28, 2012

fullscreen iOS html5 apps and pretty clouds

Recently I remade an old art project of mine, "Virtual Sisyphus", as an html5 canvas work using Processing.js:


The viewer plays the role of Sisyphus, condemned to drag a rock up a mountain for eternity. Every time you try and fail to do so, the rock scores a point. (It's an existentialist joke, and like the PHB in the Dilbert cartoon series said, it has a certain sense of playfulness, if you look at it from the rock's point of view.)

I exhibited this as a work at my company's employee art show by using an iPad as a kind of virtual frame, you can see it on the left here:


I programmed the piece to have adjustable width and height, so it could fill its container, and scale itself to make good use of the space. Then, to make it as a nice fullscreen app (and make sure all the finger moving got interpreted as clicks and mouse drags in the Sisyphus world, rather than as scaling or resize) I did the following:

In the processing.js code setup, I started with:
size(document.documentElement.clientWidth, document.documentElement.clientHeight);

Then in the html file I had the following meta tags:

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name = "format-detection" content = "telephone=no"/>
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width;" />
<meta name="apple-mobile-web-app-capable" content="yes" />

Just to be sure, I set padding and margin to 0px for both the body and the canvas tag processing.js uses.

(The apple-specific tag is kind of neat, adding that meant I could send the webpage to the homescreen from Safari, and then when I launch it from the home screen, it just has a minimal black bar at top, and skips the usual Safari address bar and what not.)

BONUS: HOW TO MAKE CLOUDS
I ended up being pleased with how nicely my cloud algorithm came out, using stretched ovals to get  a nicely organic/cartoony feel. Here was my Cloud class:

class Cloud {
  float x,y;
 ArrayList<Fluff> fluffs; 
  Cloud(){
      y = random(0,height * 3 / 8);
      fluffs = new ArrayList();
      int count = round(random(2,8));
      for(int i = 0; i < count; i++){
      fluffs.add(new Fluff());
      }
      x = width - getLeftmost();
      
 } 
   void move(){
      x -= random(.1,.5);
   }   
   float getLeftmost(){
       float lowest = 9999;
     for(Fluff f : fluffs){
       float side = f.getLeftmost();
       if(side < lowest) lowest = side;
     }
     return lowest;
   } 
   
   float getRightmost(){
       float highest = -9999;
     for(Fluff f : fluffs){
       float side = f.getRightmost();
       if(side > highest) highest = side;
     }
     return highest;
   } 
   
   void draw(){
     stroke(0);
     noStroke();
     fill(200);
     pushMatrix();
     translate(x,y);
   for(Fluff f : fluffs){
      f.draw(); 
   }
   popMatrix();
   }
  
}


class Fluff{
  float w,h,x,y;   
  Fluff(){
          w = random(width/9,width /3);
     h = random(height/24, height / 8);  
     x = random(-w/3,w/3);
     y = random(-h/3,h/3);
    
   }
   
   float getLeftmost(){
     return x-w/2;
   }  
   float getRightmost(){
     return x+w/2;
   }  
   
  void draw(){
   ellipse(x,y,w,h);
   w += random(-.1,.1);  
   h += random(-.1,.1);  
   x += random(-.1,.1);  
   y += random(-.1,.1);  

}

(Much  of the code was just getting the maximum left and right extents, so I would know when it was safe to remove a cloud from the world, and making sure it appeared off the right side of the screen.)

My main cloud generating loop was:

Cloud cloudToKill = null;

  for(Cloud c : clouds){
     c.move();
    c.draw(); 
    if(c.x + c.getRightmost() < 0){
     cloudToKill = c; //only one but it doesn't matter
    }
  }
  if(cloudToKill != null) clouds.remove(cloudToKill);
  
  if(clouds.size() < 5){
     if(random(250) < 1){
        clouds.add(new Cloud());
     } 
  }
  
It could only remove one cloud per frame, but that wasn't such a problem. And it maxed out at 5 clouds at a time.

So, that was it! Let me know if you ever manage to drag that rock to the top!


Monday, September 24, 2012

utm, no escape and irregular expressions


If you're doing work online and hang around marketing people enough, at some point "utm" might enter your world. Your landing pages will get hit with URL parameters like

utm_source=google&utm_medium=cpc&utm_term=some%25252Bgreat%25252Bkeywords&utm_campaign=Program

Beautiful, huh? ("UTM" stands for "Urchin Traffic Monitor", as Urchin was an early Google purchase and is at the core of their marketing tools.)

We were trying to use the utm_term variable, grabbing the keywords that triggered our ad (that the user then clicked) to appear on a web search. We wanted to parse the keywords to plug into our own local search engine.

I was aware aware of the %2B encoding... that's the hex code for a plus, and a plus is used to represent a space character, since URLs aren't supposed to have spaces. The double encoding (space to plus to hex) seemed a bit of overkill, but whatever...

But it wasn't enough! Searches were failing and it wasn't clear why... using our log browsing tool "Splunk" (what a name!) I grabbed log data to get the actual URLs, and found they our primitive "replace %2B with space" routine wasn't cutting it, because of beauties like "%252B" and even "%25252b" showing up. %25 is the code for the percent sign itself. So these guys weren't just doing double encoding, but triple and quadruple encoding!  Yeesh. (Meme: "Yo Dawg, we heard you like encoding, so we encoded your encoding so you can escape while you escape!")

I'm not sure if there's a handy library that would more properly do the un-escaping, but a bit of playing with javascript and actual data leads me to believe this regex should do the trick:

function removeDoubleEncodedSpaces(val){
return val.replace(/\%(25)*2b/gi," ");
}
So the pattern was a literal percent sign, 0 or more "25"s, then ending with a literal "2b", and I wanted to replace all of those with space, and do it in a case insensitive way. (Sometimes being an old Perl geek has its advantages.)

UPDATE: a coworker pointed out I could use decodeURIComponent(), a javascript built in. But I'd have to apply it 3 or 4 times in this case... I'm not sure if there's a while loop that would make sure all the encoding was taken care of.

Thursday, September 20, 2012

more html5 sound toys and games goodness

Last night I gave a brief presentation about my lowLag.js audio library at the Boston HTML5 Game Dev Meetup. I was able to show off the project's playground page with its new drum machine mini-app:

I got some nice feedback about the project from people. Brian Shim mentioned a trick of setting up a mic and getting a sound clip that has both the sound of the tap and the actual sound replay, so you can quantify the lag more precisely.

Admittedly, my humble project was a bit eclipsed by Tristan Jehan's talk about The Echo Nest. To brutally over simplify, part of their core is similar to what Pandora and the Musical Genome project do, but they use big data to get there, and when combined with an open API (and a number of music hackathons) they get some amazing stuff done... not just charting the relations between artists and songs in a metadata kind of way, but chopping and splicing and analyzing the song files themselves. Here were some of the projects mentioned:
Some of the others tools using the API seem to not be (or no longer online), like
Finally, we witnessed the launch of the Brainworth Kickstarter. The idea is a platform that teaches html5 game programming by playing games. It looks to have crazy fun social elements and general flexibility.

Monday, September 3, 2012

empower everyone: a new old paradigm for social software

Ok, enough of the in-the-trenches HTML5 audio gridning: lets talk about something a bit more abstract.

Recently I noticed a parallel in the solutions to problems faced by Google in their Hangouts app and by Apple with their AppleTV AirPlay photo sharing.

Google Hangouts is a group voice chat. That social technology often has a problem where one's person microphone is causing a problem for everyone else: either delay-echoing the main conversation, or making feedback, or having an on-device microphone amplify the typing sound of that person as they multitask during the chat, or some kind of intrusive background noise. Those sounds can be very disruptive to the group. But with Google Hangouts, everyone is empowered to mute anyone else. And they don't just mute that person for themselves, they mute that person for everyone!

If it ended there, a typical engineer might thing "That's censorship! How can that work? We should designate a conversation leader and only that person has such power!" But Google added two additional features that make it make sense: a public announcement of the muting is made on the Google Hangouts screen. ("Fred muted Barnie", for example). And a person who is muted is always empowered to unmute themselves. So while the potential for mute/unmute wars certainly exists, overall the experience makes sense.

I saw a similar problem and solution with AppleTV's AirPlay feature. We were over at our friend Sam's  house during our Labor Day vacation, and each of us had some pictures of the past few days on our iPhones that we'd like to show each other. The UI for this was breath-takingly simple in its "it just works" factor: in the iPhone photo app, hit the sharing button and then "AppleTV". And the rule is "last one in wins"; anyone can override what other people are looking at.  The stakes are pretty low, and anyone can replace what anyone else has beamed to the screen. Simplicity trumps authority!

But of course, there's nothing new under the sun, and this idea of empowering everyone has been around for a while. Here's an excerpt from Steven Levy's Hackers, Heroes of the Computer Revolution   about the ITS (Incompatible Time-Sharing System), an OS for the PDP-6:
ITS, in contrast [to Multics where hackers would prove their mettle by crashing the robust system], had a command whose specific function was crashing the system. All you had to do was type KILL SYSTEM, and the PDP-6 would grind to a halt. The idea was to take all the fun away from crashing the system by making it trivial to do that. On rare occasions, some loser would look at the available commands and say, "Wonder what KILL does?" and bring the system down, but by and large ITS proveed that the best security was no security it all.
Now it's not clear that this type of communal empowerment is always appropriate... like Alfred Pennyworth says in The Dark Knight, "some men just want to watch the world burn" and so the community needs to be able to reject such griefers with the scorn they deserve.  Still, it's a powerful idea to keep in mind for community UX.