I recently had a similar task decoding 12-bit binary 24-channel electroencephalogram data files and found that bit shifting and masking with BitAnd are the way to go. The specific approach depends on file structure, endianness and channel number. In the case of this 'format 212' binary data file containing two interlaced signals, the following procedure works well:
Read the file as 24-bit unsigned integers:
file= "http://physionet.org/physiobank/database/mitdb/100.dat";
data24 = BinaryReadList[file, "UnsignedInteger24"];
Because of byte ordering the first 12-bit signal samples (sig1) are taken from bits 13-24 and the second 12-bit signal samples (sig2) are formed by shifting bits 1-8 to the right 16 places and adding bits 9-12 shifted 4 places to the right:
sig1 = BitAnd[data24, 16^^000FFF];
sig2 = BitShiftRight[data24, 16] + BitShiftRight[BitAnd[data24, 16^^00F000], 4];
Let Export and BinaryReadList do the twos' complement:
Export["tmp1", BitShiftLeft[sig1, 4], "UnsignedInteger16"];
Export["tmp2", BitShiftLeft[sig2, 4], "UnsignedInteger16"];
sig1 = BitShiftRight[BinaryReadList["tmp1", "Integer16"], 4];
sig2 = BitShiftRight[BinaryReadList["tmp2", "Integer16"], 4];
DeleteFile["tmp1"]; DeleteFile["tmp2"];
The initial left 4-bit shift makes 16-bit values and the UnsignedInteger16 export writes them preserving sign bits. The (singed) Integer16 BinaryRead then creates the twos' complement with signing as necessary (this file happens to have no negative values). The final right 4-bit shift restores the original magnitudes. Plot:
a = 1; b = 1024;
ListLinePlot[{sig1[[a ;; b]], sig2[[a ;; b]]}, PlotRange -> All, ImageSize->600, AspectRatio -> Automatic]
This procedure's output is identical to SEngstrom's 3-byte algorithm posted above, but decodes the entire file in 0.1 s (OS X 64-bit i7).
Regards,
DBM
p.s. I also wrote a small c++ command line tool 'b12to16cl' that converts this format212 file to 16-bit binary in < 0.0005 s. It can be called from within Mathematica:
Run["/usr/local/bin/b12to16cl format212 inFilePath outFilePath"]
Then one can just BinaryReadList the tmp file as "Integer16" and delete it. Let me know if you'd like the code...
Regards