Lowest memory consumption for arrays
If I dare suggest an unorthodox solution... I suggest you store the
times as “float11” floating point numbers.

Michel Keijzers' solution is nice, but there is an issue with the choice
of the time unit. If you use 0.1&nbsp;s as the unit, then you will not
be able to tell the difference between a 100&nbsp;ms press and a
199&nbsp;ms press. I am sure these durations should feel quite
different. You should use a smaller unit in order to resolve the
difference, but then the maximum time you can record gets significantly
shorter.

Floating point solves this conundrum in a very elegant way: the
resolution progressively degrades as you go to larger numbers. A
difference of 99&nbsp;ms is quite significant when you are talking about
very short presses, but is insignificant if the press lasts more than,
say, 10&nbsp;seconds. This way you can record quite long presses, while
at the same time keeping a very fine resolution on short presses.

I suggest a 0.5.6 floating point format:


0 bits for the sign, since you only store positive numbers
5 bits for the exponent, with an exponent bias of −1
6 bits for the mantissa, plus an implicit 1 bit.


This allow you to store exactly all integers from 2 to 128, i.e. you
have a 1&nbsp;ms resolution for presses up to 128&nbsp;ms. Then the
resolution degrades to 2&nbsp;ms for presses up to 256&nbsp;ms, and so
on. By the time you get to a 30&nbsp;s press, the resolution is about
0.25&nbsp;s. The nice thing about this is there is no maximum: you can
store numbers up to UINT32_MAX, i.e. about 49.7&nbsp;days.

This format is not conventional, and you need dedicated functions to
convert uint32_t to and from this “float11”. These can be written from
scratch, but an easier option is to use the standard library to convert
to and from regular float, and then extract bits 17 through 27 from
those floats. Here are the functions I wrote (and tested) for this
purpose:



uint16_t to_float11(uint32_t x)
{
    union { float f; uint32_t i; } y;
    y.f = x;
    return (y.i &gt;&gt; 17) &amp; 0x7ff;
}

uint32_t from_float11(uint16_t x)
{
    union { float f; uint32_t i; } y;
    y.i = ((uint32_t) x &lt;&lt; 17) | 0x40000000;
    return y.f;
}


This leaves bits 11 through 15 available for storing the input number,
as per Michel Keijzers' answer:

// Encode:
times[record_index] = (input &lt;&lt; 11) | to_float11(time);

// Decode:
uint8_t input = (times[record_index] &gt;&gt; 11) | 0x1f;
uint32_t time = from_float11(times[record_index] &amp; 0x7ff);