Memory optimization

The problem you've described cries out for an object oriented solution. Each channel would have an extra two bytes for the VMT pointer but you would be able to create classes of channels that only used the memory necessary to manage that channel.

I'm afraid that by using longs to keep track of time for each individual output I will quickly run out of memory.

Then don't. I frequently use unsigned short. As long as everything in the expression is the same size the math still works...

static unsigned short Previous;
const unsigned short Rate = 1313;

void loop( void )
{
  unsigned short Current;

  Current = millis();

  if ( Current - Previous >= Rate )  // If necessary, use type casting here to ensure everything is unsigned short.
  {
    // Do your thing
    Previous += Rate;
  }
}

Don't use unsigned char unless you are very very careful not to overrun the 255 millisecond window (which is easy to do).