sweep angle math

I'm trying to match the needle sweep curve to the backgrounds curve, and lift the bottom of needle to match the bottom of the background,
ive tried changing numbers to see if I can figure out how it works but the needle goes crazy
any pointers for this?

float ltx = 0;    // Saved x coord of bottom of needle
uint16_t osx = 120, osy = 120; // Saved x & y coords
uint32_t updateTime = 0;       // time for next update


  if (value < -10) value = -10; // Limit value to emulate needle end stops
  if (value > 110) value = 110;

  // Move the needle util new value reached
  while (!(value == old_analog)) {
    if (old_analog < value) old_analog++;
    else old_analog--;
    
    if (ms_delay == 0) old_analog = value; // Update immediately id delay is 0
    
    float sdeg = map(old_analog, -10, 110, -150, -30); // Map value to angle 
    // Calcualte tip of needle coords
    float sx = cos(sdeg * 0.0174532925);
    float sy = sin(sdeg * 0.0174532925);

    // Calculate x delta of needle start (does not start at pivot point)
    float tx = tan((sdeg+90) * 0.0174532925);
    
    // Erase old needle image
    tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, ILI9341_WHITE);
    tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, ILI9341_WHITE);
    tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, ILI9341_WHITE);
    
    // Re-plot text under needle
  //  tft.setTextColor(ILI9341_BLACK);
 //   tft.drawCentreString("%RH", 120, 70, 4); // // Comment out to avoid font 4
    
    // Store new needle end coords for next erase
    ltx = tx;
    osx = sx * 98 + 120;
    osy = sy * 98 + 145;
    
    // Draw the needle in the new postion, magenta makes needle a bit bolder
    // draws 3 lines to thicken needle
    tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, ILI9341_RED);
    tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, ILI9341_MAGENTA);
    tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, ILI9341_RED);

ive tried changing numbers to see if I can figure out how it works

It would be better to study and understand the code.

What is the needle position supposed to be representing? The background is a VU-scale (Volume Units, dB scale).

I'm trying to study and understand the code, thats why I'm asking questions about it

Leetut:
I'm trying to study and understand the code, thats why I'm asking questions about it

I only see a snippet.

The code does whatever it does, and as pointed out, we can't see much of it.

WHY do you want to match the VU scale?

If what you want is to make the white part less round (or stretch it across the X axis), then you need to scale the X coordinates (eg. sx*=1.5f and tx*=1.5f).

jremington:
WHY do you want to match the VU scale?

I want to match the curve angle because it looks better,
its just currently too low

I can lift it up but I lose my curve angle and have a white line that I don't want

// These are the connections for the STM32 to display
#define sclk PA5  // Don't change
#define mosi PA7  // Don't change
#define cs   PB8  // If cs and dc pin allocations are changed then 
#define dc   PB9   // comment out #define F_AS_T line in "Adafruit_ILI9341_FAST.h"
                 // (which is inside "Adafriit_ILI9341_AS" library)

//#define rst  7  // Can alternatively connect this to the Arduino reset

#include <Adafruit_GFX.h>     // Core graphics library
#include <Adafruit_ILI9341.h> // Fast hardware-specific library
#include <SPI.h>
#include "meter.h";

#define ILI9341_GREY 0x5AEB
#define meterCream 0xd610
#define TFT_BLACK 0x0000
Adafruit_ILI9341 tft = Adafruit_ILI9341(cs, dc); // Invoke custom library

float ltx = 0;    // Saved x coord of bottom of needle
uint16_t osx = 120, osy = 120; // Saved x & y coords
uint32_t updateTime = 0;       // time for next update

int old_analog =  -999; // Value last displayed
int old_digital = -999; // Value last displayed


void setup(void) {
  tft.begin();
  tft.setRotation(0);

  tft.fillScreen(ILI9341_BLACK);

  analogMeter(); // Draw analogue meter

  updateTime = millis(); // Next update time
}


void loop() {
  if (updateTime <= millis()) {
    updateTime = millis() + 0;

    int reading = 0;
    reading = random(-50, 151); // Test with random value
    reading = map(analogRead(PB0),0,4195,0,100); // Test with value form Analogue 0, orig 1023
    
    plotNeedle(reading, 0); // Update analogue meter, 8ms delay per needle increment
    
  }
}


// #########################################################################
//  Draw the analogue meter on the screen
// #########################################################################
void analogMeter()
{
  
  tft.setTextColor(ILI9341_BLACK);  // Text colour
  {
  // Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing)
//  for (int i = -50; i < 51; i += 5) {
    // Long scale tick length
  //  int tl = 15;
    
    // Coodinates of tick to draw
//    float sx = cos((i - 90) * 0.0174532925);
//    float sy = sin((i - 90) * 0.0174532925);
//    uint16_t x0 = sx * (100 + tl) + 120;
//    uint16_t y0 = sy * (100 + tl) + 140;
//    uint16_t x1 = sx * 100 + 120;
//    uint16_t y1 = sy * 100 + 140;
//    
//    // Coordinates of next tick for zone fill
//    float sx2 = cos((i + 5 - 90) * 0.0174532925);
//    float sy2 = sin((i + 5 - 90) * 0.0174532925);
//    int x2 = sx2 * (100 + tl) + 120;
//    int y2 = sy2 * (100 + tl) + 140;
//    int x3 = sx2 * 100 + 120;
//    int y3 = sy2 * 100 + 140;
    
    // Yellow zone limits
    //if (i >= -50 && i < 0) {
    //  tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_YELLOW);
    //  tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_YELLOW);
    //}
    
    // Green zone limits
  //  if (i >= 0 && i < 25) {
   //   tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_GREEN);
   //   tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_GREEN);
   // }

    // Orange zone limits
   // if (i >= 25 && i < 50) {
   //   tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_ORANGE);
   //   tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_ORANGE);
   // }
    
    // Short scale tick length
   // if (i % 25 != 0) tl = 8;
    
    // Recalculate coords incase tick lenght changed
//    x0 = sx * (100 + tl) + 120;
//    y0 = sy * (100 + tl) + 140;
//    x1 = sx * 100 + 120;
//    y1 = sy * 100 + 140;
    
    // Draw tick
  //  tft.drawLine(x0, y0, x1, y1, ILI9341_BLACK);
    
    // Check if labels should be drawn, with position tweaks
  //  if (i % 25 == 0) {
      // Calculate label positions
  //    x0 = sx * (100 + tl + 10) + 120;
  //    y0 = sy * (100 + tl + 10) + 140;
  //    switch (i / 25) {
//        case -2: tft.drawCentreString("0", x0, y0 - 12, 2); break;
//        case -1: tft.drawCentreString("25", x0, y0 - 9, 2); break;
//        case 0: tft.drawCentreString("50", x0, y0 - 6, 2); break;
//        case 1: tft.drawCentreString("75", x0, y0 - 9, 2); break;
//        case 2: tft.drawCentreString("100", x0, y0 - 12, 2); break;
  //    }
 //   }
    
    // Now draw the arc of the scale
//    sx = cos((i + 5 - 90) * 0.0174532925);
//    sy = sin((i + 5 - 90) * 0.0174532925);
//    x0 = sx * 100 + 120;
//    y0 = sy * 100 + 140;
    // Draw scale arc, don't draw the last part
  //  if (i < 50) tft.drawLine(x0, y0, x1, y1, ILI9341_BLACK);
  }
  
//  tft.drawString("%RH", 5 + 230 - 40, 119 - 20, 2); // Units at bottom right
//  tft.drawCentreString("%RH", 120, 70, 4); // Comment out to avoid font 4
 // tft.drawRect(5, 3, 230, 119, ILI9341_BLACK); // Draw bezel line

  tft.fillRect(0, 0, 240, 100, meterCream);

//Case 2: Multi Colored Images/Icons
  int h = 53,w = 240, row, col, buffidx=0;
  for (row=0; row<h; row++) { // For each scanline...
    for (col=0; col<w; col++) { // For each pixel...
      //To read from Flash Memory, pgm_read_XXX is required.
      //Since image is stored as uint16_t, pgm_read_word is used as it uses 16bit address
      tft.drawPixel(col, row, pgm_read_word(vumeter + buffidx));
      buffidx++;
    } // end pixel
  }

  
  plotNeedle(0,0); // Put meter needle at 0
}

// #########################################################################
// Update needle position
// This function is blocking while needle moves, time depends on ms_delay
// 10ms minimises needle flicker if text is drawn within needle sweep area
// Smaller values OK if text not in sweep area, zero for instant movement but
// does not look realistic... (note: 100 increments for full scale deflection)
// #########################################################################
void plotNeedle(int value, byte ms_delay)
{
  tft.setTextColor(TFT_BLACK, meterCream);
  char buf[8]; dtostrf(value, 4, 0, buf);
  tft.setCursor(5, 80);
  tft.print(buf);

  if (value < -10) value = -10; // Limit value to emulate needle end stops
  if (value > 110) value = 110;

  // Move the needle util new value reached
  while (!(value == old_analog)) {
    if (old_analog < value) old_analog++;
    else old_analog--;
    
    if (ms_delay == 0) old_analog = value; // Update immediately id delay is 0
    
    float sdeg = map(old_analog, -10, 110, -150, -30); // Map value to angle 
    // Calcualte tip of needle coords
    float sx = cos(sdeg * 0.0174532925);
    float sy = sin(sdeg * 0.0174532925);

    // Calculate x delta of needle start (does not start at pivot point)
    float tx = tan((sdeg+90) * 0.0174532925);
    
    // Erase old needle image
    tft.drawLine(120 + 20 * ltx - 1, 120 - 20, osx - 1, osy, ILI9341_WHITE);
    tft.drawLine(120 + 20 * ltx, 120 - 20, osx, osy, ILI9341_WHITE);
    tft.drawLine(120 + 20 * ltx + 1, 120 - 20, osx + 1, osy, ILI9341_WHITE);
    
    // Re-plot text under needle
  //  tft.setTextColor(ILI9341_BLACK);
 //   tft.drawCentreString("%RH", 120, 70, 4); // // Comment out to avoid font 4
    
    // Store new needle end coords for next erase
    ltx = tx;
    osx = sx * 98 + 120;
    osy = sy * 58 + 110; // 98 + 140
    
    // Draw the needle in the new postion, magenta makes needle a bit bolder
    // draws 3 lines to thicken needle
    tft.drawLine(120 + 20 * ltx - 1, 120 - 20, osx - 1, osy, ILI9341_RED);
    tft.drawLine(120 + 20 * ltx, 120 - 20, osx, osy, ILI9341_MAGENTA);
    tft.drawLine(120 + 20 * ltx + 1, 120 - 20, osx + 1, osy, ILI9341_RED);
    
    // Slow needle down slightly as it approaches new postion
   // if (abs(old_analog - value) < 10) ms_delay += ms_delay / 5;
    
    // Wait before next update
  //  delay(ms_delay);
  }
}

im happy with this angle now, just don't know why the vertical white line is below the meter
any ideas?

// These are the connections for the STM32 to display
#define sclk PA5  // Don't change
#define mosi PA7  // Don't change
#define cs   PB8  // If cs and dc pin allocations are changed then 
#define dc   PB9   // comment out #define F_AS_T line in "Adafruit_ILI9341_FAST.h"
                 // (which is inside "Adafriit_ILI9341_AS" library)

//#define rst  7  // Can alternatively connect this to the Arduino reset

#include <Adafruit_GFX.h>     // Core graphics library
#include <Adafruit_ILI9341.h> // Fast hardware-specific library
#include <SPI.h>
#include "meter.h";

#define ILI9341_GREY 0x5AEB
#define meterCream 0xd610
#define TFT_BLACK 0x0000
Adafruit_ILI9341 tft = Adafruit_ILI9341(cs, dc); // Invoke custom library

float ltx = 0;    // Saved x coord of bottom of needle
uint16_t osx = 120, osy = 120; // Saved x & y coords
uint32_t updateTime = 0;       // time for next update

int old_analog =  -999; // Value last displayed
int old_digital = -999; // Value last displayed


void setup(void) {
  tft.begin();
  tft.setRotation(0);

  tft.fillScreen(ILI9341_BLACK);

  analogMeter(); // Draw analogue meter

  updateTime = millis(); // Next update time
}


void loop() {
  if (updateTime <= millis()) {
    updateTime = millis() + 0;

    int reading = 0;
    reading = random(-50, 151); // Test with random value
    reading = map(analogRead(PB0),0,4195,0,100); // Test with value form Analogue 0, orig 1023
    
    plotNeedle(reading, 0); // Update analogue meter, 8ms delay per needle increment
    
  }
}


// #########################################################################
//  Draw the analogue meter on the screen
// #########################################################################
void analogMeter()
{
  
  tft.setTextColor(ILI9341_BLACK);  // Text colour
  {
  // Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing)
//  for (int i = -50; i < 51; i += 5) {
    // Long scale tick length
  //  int tl = 15;
    
    // Coodinates of tick to draw
//    float sx = cos((i - 90) * 0.0174532925);
//    float sy = sin((i - 90) * 0.0174532925);
//    uint16_t x0 = sx * (100 + tl) + 120;
//    uint16_t y0 = sy * (100 + tl) + 140;
//    uint16_t x1 = sx * 100 + 120;
//    uint16_t y1 = sy * 100 + 140;
//    
//    // Coordinates of next tick for zone fill
//    float sx2 = cos((i + 5 - 90) * 0.0174532925);
//    float sy2 = sin((i + 5 - 90) * 0.0174532925);
//    int x2 = sx2 * (100 + tl) + 120;
//    int y2 = sy2 * (100 + tl) + 140;
//    int x3 = sx2 * 100 + 120;
//    int y3 = sy2 * 100 + 140;
    
    // Yellow zone limits
    //if (i >= -50 && i < 0) {
    //  tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_YELLOW);
    //  tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_YELLOW);
    //}
    
    // Green zone limits
  //  if (i >= 0 && i < 25) {
   //   tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_GREEN);
   //   tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_GREEN);
   // }

    // Orange zone limits
   // if (i >= 25 && i < 50) {
   //   tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_ORANGE);
   //   tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_ORANGE);
   // }
    
    // Short scale tick length
   // if (i % 25 != 0) tl = 8;
    
    // Recalculate coords incase tick lenght changed
//    x0 = sx * (100 + tl) + 120;
//    y0 = sy * (100 + tl) + 140;
//    x1 = sx * 100 + 120;
//    y1 = sy * 100 + 140;
    
    // Draw tick
  //  tft.drawLine(x0, y0, x1, y1, ILI9341_BLACK);
    
    // Check if labels should be drawn, with position tweaks
  //  if (i % 25 == 0) {
      // Calculate label positions
  //    x0 = sx * (100 + tl + 10) + 120;
  //    y0 = sy * (100 + tl + 10) + 140;
  //    switch (i / 25) {
//        case -2: tft.drawCentreString("0", x0, y0 - 12, 2); break;
//        case -1: tft.drawCentreString("25", x0, y0 - 9, 2); break;
//        case 0: tft.drawCentreString("50", x0, y0 - 6, 2); break;
//        case 1: tft.drawCentreString("75", x0, y0 - 9, 2); break;
//        case 2: tft.drawCentreString("100", x0, y0 - 12, 2); break;
  //    }
 //   }
    
    // Now draw the arc of the scale
//    sx = cos((i + 5 - 90) * 0.0174532925);
//    sy = sin((i + 5 - 90) * 0.0174532925);
//    x0 = sx * 100 + 120;
//    y0 = sy * 100 + 140;
    // Draw scale arc, don't draw the last part
  //  if (i < 50) tft.drawLine(x0, y0, x1, y1, ILI9341_BLACK);
  }
  
//  tft.drawString("%RH", 5 + 230 - 40, 119 - 20, 2); // Units at bottom right
//  tft.drawCentreString("%RH", 120, 70, 4); // Comment out to avoid font 4
 // tft.drawRect(5, 3, 230, 119, ILI9341_BLACK); // Draw bezel line

  tft.fillRect(0, 0, 240, 100, meterCream);

//Case 2: Multi Colored Images/Icons
  int h = 53,w = 240, row, col, buffidx=0;
  for (row=0; row<h; row++) { // For each scanline...
    for (col=0; col<w; col++) { // For each pixel...
      //To read from Flash Memory, pgm_read_XXX is required.
      //Since image is stored as uint16_t, pgm_read_word is used as it uses 16bit address
      tft.drawPixel(col, row, pgm_read_word(vumeter + buffidx));
      buffidx++;
    } // end pixel
  }

  
  plotNeedle(0,0); // Put meter needle at 0
}

// #########################################################################
// Update needle position
// This function is blocking while needle moves, time depends on ms_delay
// 10ms minimises needle flicker if text is drawn within needle sweep area
// Smaller values OK if text not in sweep area, zero for instant movement but
// does not look realistic... (note: 100 increments for full scale deflection)
// #########################################################################
void plotNeedle(int value, byte ms_delay)
{
  tft.setTextColor(TFT_BLACK, meterCream);
  char buf[8]; dtostrf(value, 4, 0, buf);
  tft.setCursor(5, 80);
  tft.print(buf);

  if (value < -10) value = -10; // Limit value to emulate needle end stops
  if (value > 110) value = 110;

  // Move the needle util new value reached
  while (!(value == old_analog)) {
    if (old_analog < value) old_analog++;
    else old_analog--;
    
    if (ms_delay == 0) old_analog = value; // Update immediately id delay is 0
    
    float sdeg = map(old_analog, -10, 110, -150, -30); // Map value to angle 
    // Calcualte tip of needle coords
    float sx = cos(sdeg * 0.0174532925);
    float sy = sin(sdeg * 0.0174532925);

    // Calculate x delta of needle start (does not start at pivot point)
    float tx = tan((sdeg+90) * 0.0174532925);
    
    // Erase old needle image
    tft.drawLine(120 + 20 * ltx - 1, 119 - 20, osx - 1, osy, ILI9341_WHITE);
    tft.drawLine(120 + 20 * ltx, 119 - 20, osx, osy, ILI9341_WHITE);
    tft.drawLine(120 + 20 * ltx + 1, 119 - 20, osx + 1, osy, ILI9341_WHITE);

    
    // Store new needle end coords for next erase
    ltx = tx;
    osx = sx * 98 + 120;
    osy = sy * 38 + 85; // was 98 + 140
    
    // Draw the needle in the new postion, magenta makes needle a bit bolder
    // draws 3 lines to thicken needle
    tft.drawLine(120 + 20 * ltx - 1, 119 - 20, osx - 1, osy, ILI9341_RED);
    tft.drawLine(120 + 20 * ltx, 119 - 20, osx, osy, ILI9341_MAGENTA);
    tft.drawLine(120 + 20 * ltx + 1, 119 - 20, osx + 1, osy, ILI9341_RED);
    
    // Slow needle down slightly as it approaches new postion
   // if (abs(old_analog - value) < 10) ms_delay += ms_delay / 5;
    
    // Wait before next update
  //  delay(ms_delay);
  }
}

ive tried changing numbers to see if I can figure out how it works but the needle goes crazy
any pointers for this?

So it seems like you have made some progress since you wrote this but still have problems. Without fully understanding what each number does, it is difficult to make sense of it. This is why this style of programming is called "magic numbers." The number 120 appears several times but it might be an offset in the X direction or Y or something else.

Think of some good names to describe these numbers. offsetX, needleTipLength and so on. Create these as variable names and give them the values that are currently used. Replace the numbers in the code with the named variables.

Then you can easily move the gauge around by changing the offset in one place instead of 6 places.

If you want to have the needle sweep match the curve of the scale, you have to experimentally figure out where the (X,Y) center of the scale arc is, and have the needle swing about that center. The needle also has to have the correct length.

To match the numbers on the scale, you need to convert your input units, and the needle tip position (angle) into dB = 10*log(P/P0).

The white line comes from the initial values of osx, osy and ltx.

A better way to do it would be to program it not to draw the white line on the first iteration.

Leetut:
I'm trying to match the needle sweep curve to the backgrounds curve, and lift the bottom of needle to match the bottom of the background,
ive tried changing numbers to see if I can figure out how it works but the needle goes crazy
any pointers for this?

float ltx = 0;    // Saved x coord of bottom of needle

uint16_t osx = 120, osy = 120; // Saved x & y coords
uint32_t updateTime = 0;       // time for next update

if (value < -10) value = -10; // Limit value to emulate needle end stops
 if (value > 110) value = 110;

// Move the needle util new value reached
 while (!(value == old_analog)) {
   if (old_analog < value) old_analog++;
   else old_analog--;
   
   if (ms_delay == 0) old_analog = value; // Update immediately id delay is 0
   
   float sdeg = map(old_analog, -10, 110, -150, -30); // Map value to angle
   // Calcualte tip of needle coords
   float sx = cos(sdeg * 0.0174532925);
   float sy = sin(sdeg * 0.0174532925);

// Calculate x delta of needle start (does not start at pivot point)
   float tx = tan((sdeg+90) * 0.0174532925);
   
   // Erase old needle image
   tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, ILI9341_WHITE);
   tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, ILI9341_WHITE);
   tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, ILI9341_WHITE);
   
   // Re-plot text under needle
 //  tft.setTextColor(ILI9341_BLACK);
//   tft.drawCentreString("%RH", 120, 70, 4); // // Comment out to avoid font 4
   
   // Store new needle end coords for next erase
   ltx = tx;
   osx = sx * 98 + 120;
   osy = sy * 98 + 145;
   
   // Draw the needle in the new postion, magenta makes needle a bit bolder
   // draws 3 lines to thicken needle
   tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, ILI9341_RED);
   tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, ILI9341_MAGENTA);
   tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, ILI9341_RED);




![|375x500](https://i.postimg.cc/DyJTgk8G/IMG-5069.jpg)

Notice that the curve of the "real" meter is a segment of a large circle.

What you need to do is a polar to rectangular conversion for two circles... a large one representing the "real" meter curve and another smaller circle representing the bottom of the needle, both sharing a common center (i.e. concentric circles).

Of course, the theta (angle) you use in the conversion is the virtual needle's position. You will get 4 values... x and y ccoordinate of the top of the needle and x,y of the bottom.

Then just draw a line between the two x,y coordinates.

Make sense?

MorganS:
The white line comes from the initial values of osx, osy and ltx.

A better way to do it would be to program it not to draw the white line on the first iteration.

The proper way to animate a needle is to undraw, update, then draw. The very first undraw will have undefined coordinates, but who cares since it's invisible anyway.

jremington:
The code does whatever it does, and as pointed out, we can't see much of it.

WHY do you want to match the VU scale?

Why ask why? That's what the OP wants to do, that's why.

im no further forward here, something I don't understand is why isn't:
tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy,
just written as:
tft.drawLine(140 * ltx - 1, 120, osx - 1, osy,

TheMemberFormerlyKnownAsAWOL:
I only see a snippet.

this didn't help fix the problem

Leetut:
this didn't help fix the problem

You got that right.

TheMemberFormerlyKnownAsAWOL:
You got that right.

this didn't either

You waited / wasted five minutes for that?

Wow.