user67474: I have this long array of 64 values going from 0 to 15, values which only cost 8 bits, for a pulse width modulation.
So I made a test to see its cost in space, commenting one of the two arrays.
const byte wave[64] = {8,9,9,10,11,11,12,12,13,13,14,14,14,15,15,15,15,15,15,15,14,14,14,13,13,12,12,11,11,10,9,9,8,7,7,6,5,5,4,4,3,3,2,2,2,1,1,1,1,1,1,1,2,2,2,3,3,4,4,5,5,6,7,7};
//const byte wave[2] = {8,9};
byte i = 0;
void setup() {pinMode(1,OUTPUT);}
void loop() {            //(Don't worry about the code in here, 
  analogWrite(1,wave[i]);//it's just to get the program 
  i++;delay(1);          //to hold on to the data)
}

The compiler said Sketch uses 1112 and 1050 bytes (3%) of program storage space. or a difference of 62 bytes.
This tells me that one could save a lot of memory by keeping them in a 4-bit or what I'd like to call half byte, going from 0 to f rather than from 00 to ff.
How could I do so without turning the code to unreadable bit mush?

user313: Keeping your 4-bit values in half-bytes is doable using structs and bitfields. You'll want an access function to separate the "array index" into a byte-index and a nibble selector (the LSb of the "array index") and return or store the appropriate nibble.
This related question on StackOverflow has some answers showing ways to do this. There will be obviously be some overhead to unpacking 4-bit nibbles since they can't be addressed directly, but it will be minor and is the trade-off for saving memory when memory is scarce.

user66695: If the point is to save program storage space, you're probably going to have to live with some unreadable bit mush or do bit-swapping in your head. Here's a code example that encodes your same array as a 32-byte array, but you have to combine pairs of your original numbers into bytes. It's not especially difficult because each hex digit represents one of your original numbers.
I.e., 8,9 becomes 0x89. Likewise, A is 10, B is 11, etc. Then you can iterate 1 thru 64 and do some division and bit-shifting to pull out the original value you wanted. This will iterate through your 32-byte array and pull out 64 half-byte values.
    const byte wave[32] = {0x89,0x9A,0xBB,0xCC,0xDD,0xEE,0xEF,0xFF,0xFF,0xFF,0xEE,0xED,0xDC,0xCB,0xBA,0x99,0x87,0x76,0x55,0x44,0x33,0x22,0x21,0x11,0x11,0x11,0x22,0x23,0x34,0x45,0x56,0x77};
    for(int i=0; i<64; i++) {
        byte val;
        if (i % 2) {
            // the little byte is first (LITTLE ENDIAN)
            val = wave[i/2] & 0x0F;
        } else {
            // the BIG byte is second
            val = (wave[i/2] >> 4) & 0x0F;
        }
        Serial.printf("%i=%i\n", i, val);
    }

NOTE: that the Endianness if your system may be different than mine. Most systems are little-endian these days, but you never know.

67474: <p>I have this long array of 64 values going from 0 to 15, values which only cost 8 bits, for a pulse width modulation.</p>
<p>So I made a test to see its cost in space, commenting one of the two arrays.</p>
<pre><code>const byte wave[64] = {8,9,9,10,11,11,12,12,13,13,14,14,14,15,15,15,15,15,15,15,14,14,14,13,13,12,12,11,11,10,9,9,8,7,7,6,5,5,4,4,3,3,2,2,2,1,1,1,1,1,1,1,2,2,2,3,3,4,4,5,5,6,7,7};
//const byte wave[2] = {8,9};
byte i = 0;
void setup() {pinMode(1,OUTPUT);}
void loop() {            //(Don't worry about the code in here, 
  analogWrite(1,wave[i]);//it's just to get the program 
  i++;delay(1);          //to hold on to the data)
}
</code></pre>
<p>The compiler said <code>Sketch uses 1112</code> and <code>1050 bytes (3%) of program storage space.</code> or a difference of 62 bytes.</p>
<p>This tells me that one could save a lot of memory by keeping them in a 4-bit or what I'd like to call half byte, going from <code>0</code> to <code>f</code> rather than from <code>00</code> to <code>ff</code>.</p>
<p>How could I do so without turning the code to unreadable bit mush?</p>
 313: <p>Keeping your 4-bit values in half-bytes is doable using structs and bitfields. You'll want an access function to separate the &quot;array index&quot; into a byte-index and a nibble selector (the LSb of the &quot;array index&quot;) and return or store the appropriate nibble.</p>
<p>This <a href="https://stackoverflow.com/questions/41918433/is-it-possible-to-use-array-of-bit-fields#41918448">related question on StackOverflow</a> has some answers showing ways to do this. There will be obviously be some overhead to unpacking 4-bit nibbles since they can't be addressed directly, but it will be minor and is the trade-off for saving memory when memory is scarce.</p>
 66695: <p>If the point is to save program storage space, you're probably going to have to live with some unreadable bit mush or do bit-swapping in your head. Here's a code example that encodes your same array as a 32-byte array, but you have to combine pairs of your original numbers into bytes. It's not especially difficult because each hex digit represents one of your original numbers.</p>
<p>I.e., <code>8,9</code> becomes <code>0x89</code>. Likewise, A is 10, B is 11, etc. Then you can iterate 1 thru 64 and do some division and bit-shifting to pull out the original value you wanted. This will iterate through your 32-byte array and pull out 64 half-byte values.</p>
<pre><code>    const byte wave[32] = {0x89,0x9A,0xBB,0xCC,0xDD,0xEE,0xEF,0xFF,0xFF,0xFF,0xEE,0xED,0xDC,0xCB,0xBA,0x99,0x87,0x76,0x55,0x44,0x33,0x22,0x21,0x11,0x11,0x11,0x22,0x23,0x34,0x45,0x56,0x77};
    for(int i=0; i&lt;64; i++) {
        byte val;
        if (i % 2) {
            // the little byte is first (LITTLE ENDIAN)
            val = wave[i/2] &amp; 0x0F;
        } else {
            // the BIG byte is second
            val = (wave[i/2] &gt;&gt; 4) &amp; 0x0F;
        }
        Serial.printf(&quot;%i=%i\n&quot;, i, val);
    }
</code></pre>
<p>NOTE: that the <a href="https://en.wikipedia.org/wiki/Endianness" rel="nofollow noreferrer">Endianness</a> if your system may be different than mine. Most systems are little-endian these days, but you never know.</p>
