{"id":104418,"date":"2020-11-04T07:00:00","date_gmt":"2020-11-04T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104418"},"modified":"2020-11-04T14:51:33","modified_gmt":"2020-11-04T22:51:33","slug":"20201104-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20201104-00\/?p=104418","title":{"rendered":"What is the format of the data in the AudioBuffer memory buffer, and how do I convert it to something else?"},"content":{"rendered":"<p>The <a href=\"https:\/\/docs.microsoft.com\/uwp\/api\/Windows.Media.AudioBuffer\"> Windows Runtime AudioBuffer class<\/a> represents a buffer of audio data. What is the format of this data?<\/p>\n<p>The memory buffer you <a href=\"https:\/\/docs.microsoft.com\/uwp\/api\/windows.media.audiobuffer.createreference\"> obtain from the AudioBuffer object<\/a> takes the form of an array of audio samples. Each audio sample is a collection of IEEE single-precision floating point numbers which represent a linear range of waveform amplitude from \u22121.0 to +1.0.<\/p>\n<p>In C#, this floating point format is known as <code>System.<\/code><code>Single<\/code>. In C++, it is typically represented by <code>float<\/code>.<\/p>\n<p>Each sample contains one value for each channel, and the channels come in a specified order, described in <a href=\"https:\/\/docs.microsoft.com\/windows\/win32\/api\/mmreg\/ns-mmreg-waveformatextensible\"> the documentation for the <code>WAVE\u00adFORMAT\u00adEXTENSIBLE<\/code> structure<\/a>.<\/p>\n<p>For example, suppose you have a dual-channel audio buffer, say stereo left\/right. The table in the <code>WAVE\u00adFORMAT\u00adEXTENSIBLE<\/code> documentation says that the channels come in the order <i>left<\/i> then <i>right<\/i>. Therefore the values come in this order:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Sample index<\/th>\n<th>Channel<\/th>\n<th>Value index<\/th>\n<\/tr>\n<tr>\n<td rowspan=\"2\">0<\/td>\n<td>Left<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td>Right<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td rowspan=\"2\">1<\/td>\n<td>Left<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td>Right<\/td>\n<td>3<\/td>\n<\/tr>\n<tr>\n<td rowspan=\"2\">2<\/td>\n<td>Left<\/td>\n<td>4<\/td>\n<\/tr>\n<tr>\n<td>Right<\/td>\n<td>5<\/td>\n<\/tr>\n<tr>\n<td>\u22ee<\/td>\n<td>\u22ee<\/td>\n<td>\u22ee<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Some people call this interleaved format, but whether it&#8217;s interleaved depends on what color glasses you&#8217;re wearing.<\/p>\n<p>It&#8217;s interleaved if you look at it from the point of view of a channel, since the data from one channel is interleaved with data from the other channels.<\/p>\n<p>But it&#8217;s perfectly linear format if you look at it from the point of view of the samples, since all the data for one sample is packed together.<\/p>\n<p>Okay, next question: How do you convert this to other formats?<\/p>\n<p>Well, that depends on what other format you&#8217;re converting it to. If you&#8217;re converting to a linear format,\u00b9 then you can perform a simple linear conversion. We know that the result is going to be <var>f<\/var>(x) = <var>a<\/var><var>x<\/var> + <var>b<\/var> for some values of <var>a<\/var> and <var>b<\/var>. We just need to figure out what those values are.<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>Substitute <var>x<\/var> = \u22121.0:\u2003<\/td>\n<td><var>v<\/var><sub>min<\/sub> = <var>a<\/var> \u00d7 (\u22121) + <var>b<\/var><\/td>\n<\/tr>\n<tr>\n<td>Substitute <var>x<\/var> = +1.0:\u2003<\/td>\n<td><var>v<\/var><sub>max<\/sub> = <var>a<\/var> \u00d7 (+1) + <var>b<\/var><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Solving the system of simultaneous equations gives<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td><var>a<\/var> = (<var>v<\/var><sub>max<\/sub> \u2212 <var>v<\/var><sub>min<\/sub>)\/2<\/td>\n<\/tr>\n<tr>\n<td><var>b<\/var> = (<var>v<\/var><sub>max<\/sub> + <var>v<\/var><sub>min<\/sub>)\/2<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>There is an extra wrinkle to this formula: If the destination is an integer range, then the negative values will have one extra value of range compared to the positive values. For example, a 16-bit signed value will range from \u221232768 to + 32767. If we plug zero into the function, we get just <var>b<\/var>, which is the average between the high and low values, and which will be numerically \u2212\u00bd rather than zero.<\/p>\n<p>I don&#8217;t know how audio people usually solve this problem. One option I&#8217;ve seen is to throw out the most negative value, so the effective range for a 16-bit signed value is \u221232767 to +32767. In that case, the formula simplifies to merely multiplying by <var>v<\/var>\u2098\u2090\u2093.<\/p>\n<p>Maybe some audio people can tell me what they do here.<\/p>\n<p>\u00b9 There are nonlinear formats for audio data. For example, \u03bc-law is a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Companding\"> companding<\/a> algorithm which uses a nonlinear formula in order to express a wider dynamic range in a small space, at a cost of resolution at the extremes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s a bunch of floating point samples, in a particular order.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-104418","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It&#8217;s a bunch of floating point samples, in a particular order.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104418","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=104418"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104418\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=104418"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104418"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104418"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}