{"id":29,"date":"2013-07-29T11:51:57","date_gmt":"2013-07-29T11:51:57","guid":{"rendered":"https:\/\/blogs.scummvm.org\/richiesams\/?p=29"},"modified":"2022-05-22T20:03:41","modified_gmt":"2022-05-22T20:03:41","slug":"one-persons-data-is-another-persons-noise","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/richiesams\/2013\/07\/29\/one-persons-data-is-another-persons-noise\/","title":{"rendered":"\u201cOne person&#8217;s data is another person&#8217;s noise.\u201d"},"content":{"rendered":"<p>I know it&#8217;s been forever since I&#8217;ve done a post and I&#8217;m really sorry. I got caught up in the sound issues and panorama issues. I&#8217;m going to talk about sound in this post and then make another post about panoramas. So here we go!<\/p>\n<p>\u201cOne person&#8217;s data is another person&#8217;s noise.\u201d \u00a0\u2015 K.C. Cole<\/p>\n<p>This quote pretty much sums up my experiences with the sound decoding. I was somewhat lucky in that Marisa Chan&#8217;s source code had an implementation of sound decoding that I could model off of, but at the same time, the whole function was quite cryptic. This is mostly due to the variable &#8220;naming&#8221;. And I say &#8220;naming&#8221; in the loosest sense of the word, because most were single letters:<\/p>\n<pre class=\"brush:cpp\">void adpcm8_decode(void *in, void *out, int8_t stereo, int32_t n)\r\n{\r\n    uint8_t *m1;\r\n    uint16_t *m2;\r\n    m1 = (uint8_t *)in;\r\n    m2 = (uint16_t *)out;\r\n    uint32_t a, x, j = 0;\r\n    int32_t b, i, t[4] = {0, 0, 0, 0};\r\n\r\n    while (n)\r\n    {\r\n        a = *m1;\r\n        i = t[j+2];\r\n        x = t2[i];\r\n        b = 0;\r\n\r\n        if(a &amp; 0x40)\r\n            b += x;\r\n        if(a &amp; 0x20)\r\n            b += x &gt;&gt; 1;\r\n        if(a &amp; 0x10)\r\n            b += x &gt;&gt; 2;\r\n        if(a &amp; 8)\r\n            b += x &gt;&gt; 3;\r\n        if(a &amp; 4)\r\n            b += x &gt;&gt; 4;\r\n        if(a &amp; 2)\r\n            b += x &gt;&gt; 5;\r\n        if(a &amp; 1)\r\n            b += x &gt;&gt; 6;\r\n\r\n        if(a &amp; 0x80)\r\n            b = -b;\r\n\r\n        b += t[j];\r\n\r\n        if(b &gt; 32767)\r\n            b = 32767;\r\n        else if(b &lt; -32768)\r\n            b = -32768;\r\n\r\n        i += t1[(a &gt;&gt; 4) &amp; 7];\r\n\r\n        if(i &lt; 0)\r\n            i = 0;\r\n        else if(i &gt; 88)\r\n            i = 88;\r\n\r\n        t[j] = b;\r\n        t[j+2] = i;\r\n        j = (j + 1) &amp; stereo;\r\n        *m2 = b;\r\n\r\n        m1++;\r\n        m2++;\r\n        n--;\r\n    }\r\n}\r\n<\/pre>\n<p>No offense intended towards Marisa Chan, but that makes my eyes hurt. It made understanding the algorithm that much harder. But after talking to a couple people at ScummVM and Wikipedia-ing general sound decoding, I figured out the sound is encoded using a modified Microsoft Adaptive PCM. I&#8217;ll go ahead and post my implementation and then describe the process:<\/p>\n<pre class=\"brush:cpp\">const int16 RawZorkStream::_stepAdjustmentTable[8] = {-1, -1, -1, 1, 4, 7, 10, 12};\r\n\r\nconst int32 RawZorkStream::_amplitudeLookupTable[89] = {0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,\r\n                                                        0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F,\r\n                                                        0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,\r\n                                                        0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F,\r\n                                                        0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133, \r\n                                                        0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,\r\n                                                        0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, \r\n                                                        0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0,\r\n                                                        0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954, \r\n                                                        0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B,\r\n                                                        0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF};\r\n\r\nint RawZorkStream::readBuffer(int16 *buffer, const int numSamples) {\r\n    uint32 bytesRead = 0;\r\n\r\n    \/\/ 0: Left, 1: Right\r\n    byte channel = 0;\r\n\r\n    while (bytesRead &lt; numSamples) {\r\n        byte encodedSample = _stream-&gt;readByte();\r\n        if (_stream-&gt;eos()) {\r\n            _endOfData = true;\r\n            return bytesRead;\r\n        }\r\n        bytesRead++;\r\n\r\n        int16 index = _lastSample[channel].index;\r\n        uint32 lookUpSample = _amplitudeLookupTable[index];\r\n\r\n        int32 sample = 0;\r\n\r\n        if (encodedSample &amp; 0x40)\r\n            sample += lookUpSample;\r\n        if (encodedSample &amp; 0x20)\r\n            sample += lookUpSample &gt;&gt; 1;\r\n        if (encodedSample &amp; 0x10)\r\n            sample += lookUpSample &gt;&gt; 2;\r\n        if (encodedSample &amp; 8)\r\n            sample += lookUpSample &gt;&gt; 3;\r\n        if (encodedSample &amp; 4)\r\n            sample += lookUpSample &gt;&gt; 4;\r\n        if (encodedSample &amp; 2)\r\n            sample += lookUpSample &gt;&gt; 5;\r\n        if (encodedSample &amp; 1)\r\n            sample += lookUpSample &gt;&gt; 6;\r\n        if (encodedSample &amp; 0x80)\r\n            sample = -sample;\r\n\r\n        sample += _lastSample[channel].sample;\r\n        sample = CLIP(sample, -32768, 32767);\r\n\r\n        buffer[bytesRead - 1] = (int16)sample;\r\n\r\n        index += _stepAdjustmentTable[(encodedSample &gt;&gt; 4) &amp; 7];\r\n        index = CLIP&lt;int16&gt;(index, 0, 88);\r\n\r\n        _lastSample[channel].sample = sample;\r\n        _lastSample[channel].index = index;\r\n\r\n        \/\/ Increment and wrap the channel\r\n        channel = (channel + 1) &amp; _stereo;\r\n    }\r\n\r\n    return bytesRead;\r\n}\r\n<\/pre>\n<p>Each sample is encoded into 8 bits. The actual sound sample is read from the bits using a lookup table and an index from the previous &#8216;frame&#8217;. This is then added to the sample from last &#8216;frame&#8217;. Finally, the 4 high bits are used to set the index for the next &#8216;frame&#8217;.<\/p>\n<p>The biggest problem I ran into for sound was actually a typo on my part. The template argument for CLIP was accidentally set to a uint16 instead of a int16. This caused distortions at the extremely high and low ranges of the sound. But, this usually only occurred at the beginning and end of a sound clip. I spent days trying to figure out if I had set the initial lastSample correctly, or other random ideas. After pounding my head into the desk for 3 days, the glorious wjp came along and found my typo. After which, the sound worked perfectly. Shout out to wjp!!!!!!!!!<\/p>\n<p>There is one other bug with sound and that&#8217;s in videos. The sound has a slight &#8216;ticking&#8217;. However, clone2727 identified it potentially as a problem with the AVI decoder. In the current state, the AVI decoder puts each sound &#8216;chunk&#8217; into its own AudioStream, and then puts all the streams into a queue to be played. We&#8217;re thinking the lastSample needs to persist from chunk to chunk. However, solving this problem would take either a gross hack, or a redesign of the AVI decoder. clone2727 has taken on the task, so I&#8217;m going to leave it to him and get back to the video audio later in the project.<\/p>\n<p>Well, that&#8217;s it for this post. Sound was pretty straightforward. I was only bogged down due to some really bad typos on my part. As always, feel free to comment or ask questions.<\/p>\n<p>-RichieSams<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I know it&#8217;s been forever since I&#8217;ve done a post and I&#8217;m really sorry. I got caught up in the sound issues and panorama issues. I&#8217;m going to talk about sound in this post and then make another post about panoramas. So here we go! \u201cOne person&#8217;s data is another person&#8217;s noise.\u201d \u00a0\u2015 K.C. Cole [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-29","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/29","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/comments?post=29"}],"version-history":[{"count":1,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/29\/revisions"}],"predecessor-version":[{"id":30,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/29\/revisions\/30"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/media?parent=29"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/categories?post=29"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/tags?post=29"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}