Data AcQuisition And Real-Time AnalysisScope - Spectrum - Spectrogram - Signal Generator
Software for Windows
Science with your Sound Card!
Contact us about
BMP Image to WAV Sound and Spectrogram Image Mini-Apps
This pair of macro mini-apps allows you to convert a photo or other bitmapped image into a .WAV sound file, whose spectrogram then yields the original image in the original colors. Please note that this method uses a perfectly standard spectrogram that simply uses a specific color palette and a carefully matched frequency and amplitude range. The trick is in creating the sound file itself, to produce the needed frequencies and amplitudes.
(Note: The Txt2MIDI MIDI setup is a much simpler embodiment of this concept of sound creation to generate a specific spectrogram image. The MIDI synth is used to create tone patterns that form scrolling dot-matrix spectrogram text from messages entered into Daqarta Notes.)
Consider a typical Daqarta Spectrogram: It creates an image of sound, with time on the horizontal and frequency on the vertical, and with intensity encoded as color. For example, with the default palette a bright red region near the upper left of a spectrogram indicates a loud high-frequency tone early in the sound. Dark magenta at the same position would be a very soft tone at that time and frequency.
The sound may have many simultaneous frequency components with various amplitudes at each moment in time (each spectrogram column), such that different pixels in each column have different colors.
Thus, to create a specific image, we need to produce sound with a specific array of frequencies and amplitudes at each point in time. The BMP_to_WAV macro does just that, creating a .WAV file which also encodes the full palette plus reference information into a sound "preamble". That information can be read by the companion WAV_to_BMP macro that plays the sound and shows the image on the spectrogram in the original colors.
The BMP_to_WAV mini-app analyzes the original image by copying its palette and associating each color number (typically 0-255) with a relative loudness in dB (0-31 default). Then it reads the palette color number used by each pixel in a vertical column of the image, and stores the associated dB value in an array that represents the spectrum of that column. It consists of amplitudes at each frequency to be shown in the corresponding column of the ultimate spectrogram, from low frequencies to high.
The array is then converted into a 1024-sample waveform segment using an Inverse Fast Fourier Transform (IFFT), the opposite of the forward FFT that Daqarta uses to find the spectrum of a sound, which is one column of a spectrogram. That waveform segment is sent to a .WAV file. These operations are performed by the _IFFT_Buf1_WAV macro subroutine.
Each additional pixel column is subsequently handled the same way until the entire image is encoded in a single .WAV file. The image is thus represented by 1024 waveform samples per pixel column, times 512 columns (for an image that just fills the width of the display area), or 524288 total samples... about 11 seconds of sound at the default 48000 Hz sample rate. In addition, BMP_to_WAV includes an 8192-sample "preamble" waveform ahead of the image waveform. This holds a synchronization pulse plus encoded format identification, certain parameter values, a "perfect" flat reference spectrum, and the RGB (Red, Green, Blue) specifications for each of the palette colors to be used.
The companion WAV_to_BMP mini-app allows you to play a .WAV file previously created by BMP_to_WAV. You can hear the sound as well as see the image being reconstructed by the Daqarta Spectrogram.
WAV_to_BMP reads the preamble to verify the format, and uses the sync pulse to adjust the playback analysis so that each 1024-sample frame will be properly aligned to produce the correct spectrogram column.
If you are using sound produced by a speaker and monitored by a microphone (instead of simply monitoring the raw waveform sent to the speaker), WAV_to_BMP will use the reference spectrum in the preamble to correct for speaker and microphone irregularities that affect the overall frequency response.
It also decodes the color palette from the preamble and installs it for use by the spectrogram.
Most importantly, it carefully sets the dB range of the spectrogram to exactly match that used by BMP_to_WAV, so that the loudness at each pixel frequency produces the proper original color. Since by default BMP_to_WAV encodes 256 colors into a 31 dB range, adjacent colors are represented by a loudness difference of only 0.12 dB. This would present only a minor problem in range settting on a monochrome image, but for color images it is absolutely critical.
To understand why, consider that in a monochrome image ("black and white", also known as "grayscale") the palette is a continuum of "256 shades of gray". The difference between adjacent shades is nearly undetectable. (You can see this for yourself by opening the Sgram/PT dialog, clicking on Color Palette at the top, then Load Pal, then select GrayScale.PAL.)
In such a case, shifting the dB range up or down simply changes the overall brightness of the image, while using a smaller or larger range just changes the contrast. Casual adjustment "by eye" is fine.
But in a color image the 256 palette colors are distinct, and typically not in a useful continuum. (You'll see how discontinuous the palette is when you run either BMP_to_WAV or WAV_to_BMP.)
Consider a medium red pixel from the lip region of a face: If the range is off by even a tiny amount, an adjacent palette color may appear instead. But it is unlikely to be a slightly lighter or darker shade of red, and could easily be green or blue!
There are a few requirements for any image you use. The first is due to fact that the Daqarta display area limits the spectrogram to only 512 horizontal time points, and 256 discrete frequencies on the vertical axis (without combining adjacent values or interpolating, which would be an absolute disaster due to the palette issues noted above). So the image is restricted to 512 pixels wide by 256 pixels tall. If you use a taller image, only the bottom 256 pixels will be encoded; if you use a wider image you'll see the whole thing, but not all at one time; it will just scroll past like a panoramic scan.
Second, Daqarta uses a palette of 256 colors to represent intensity, as shown via the vertical color bar at the right edge of the spectrogram. So the image must be limited to no more than 256 different colors. (They don't have to be the same colors as the default palette.)
Third, the image must be in 256-color "bitmapped" format, with a .BMP file extension. Also, for ease in processing, the file image must be rotated clockwise by 90 degrees from the desired viewing angle.
These requirements mean that some image manipulation will be needed ahead of time. If you are starting with a .JPG photo, you will need image manipulation software like GIMP (free) or Photoshop (pricey) or the equivalent. If you are starting with artwork such as a logo or drawing that already has a limited number of colors, Windows Paint is fine; photos, however, will come out as "art", like comic or graphic novel art.
These are all derived from the same "SpectroGramma" original:
This image was chosen as an "acid test" due to the wide brightness and color range, plus the shadowed facial gradients. The bright white building columns represent many simultaneous high-amplitude tones in their respective spectrogram columns.
These images were prepared with different color resolutions in GIMP. The lower resolutions may be helpful for reduced sensitivity to frequency response variations, distortion, and/or room noise if you are using acoustic WAV_to_BMP... see the Acoustic WAV_to_BMP subtopic below for more details. Otherwise, the 256-color version will give the best image.
SpectroGramma256.BMP SpectroGramma128.BMP SpectroGramma64.BMP SpectroGramma32.BMP
GIMP (GNU Image Manipulation Program) is free, open-source software. It is very powerful, akin to expensive programs like Adobe Photoshop, and likewise has a very steep learning curve. But you only need a few of its vast array of features in order to prepare images for BMP_to_WAV, and if you carefully follow the steps below you should have no problems. Be forewarned, however, that if you start exploring the feature terrain you can easily end up lost in the deep woods!
Download and install GIMP for Windows from <http://www.gimp.org/downloads> . (There are versions there for Linux and other systems; you'll need to scroll down to get the version for Windows.) Note that the first time you run GIMP the startup progress bar will stall for a LONG time while it searches your computer for fonts, maybe a minute or more. This is perfectly normal.
Once GIMP is up and running, click on the little dotted rectangle at the upper left of the tool dialog so you will be ready to select rectangular regions (instead of painting on them with the default brush tool).
Then click File in the menu bar and select Open. Double-click on locations or drives in the left-hand Places panel to select the location of the image you want to use, then browse for the proper folder in the center panel. Once you are in that folder, you can single-click on a file name to see a small preview in the right panel. Click Open when you find the one you want.
When the file opens you will see the entire image, resized as needed to fit your screen. The actual image dimensions in pixels are shown in the title bar at the top of the frame, and the percentage it has been reduced for viewing will be shown at the bottom. Your goal now will be to crop and/or shrink the actual image (not just the view of it) to fit into 512 by 256 pixels.
As you move the cursor over the image, a readout at the lower left will show its X and Y coordinates. You can use those to get an idea of how small 512 by 256 pixels looks as a fraction of the whole image; almost certainly it won't include enough of the region of interest, so you'll need to shrink the image to fit. This will essentially merge adjacent pixels; you'll get fewer total pixels, so 512 by 256 will cover a larger portion of the image.
Click on Image in the menu bar and select Scale Image. You'll see Width and Height controls showing the current dimensions in pixels, with a little chain symbol next to the controls. That means that changing either Width or Height will change the other to keep the same proportions, which is what you want.
For example, if you have an original 4000 x 3000 (12 MP) image, reducing it to 25% (1000 x 750) is probably a good starting point. You can set Width to 1000 and Height will automatically go to 750. Alternatively, you can change the default 'pixels' units to 'percent' and just enter 25 in either Width or Height.
Since the target dimensions are 512 by 256 (a 2:1 proportion that probably doesn't match your original image), you will have to crop the image as well; allow for that when you are selecting the Scale Image dimensions. Alternatively, you can crop first and scale afterwards, or even use multiple passes of each.
After the Scale Image operation, you can re-enlarge the view via the percent control at the bottom of the frame.
To crop the resulting image, click at the upper left of the region you want to keep, and drag to the lower right. A thin rectangle will show the drag region, changing to a "crawling ants" rectangle when you release the mouse button. If you don't like that rectangle, you can start another only if its upper left corner is outside of the current one; otherwise, click on Select - None. (Alternatively, you can click on Edit - Undo Rect Select.) Then you can select another rectangle as described.
When you have the rectangle you want, click on Image in the menu bar again, and select Crop To Selection. After the crop the image will still have the "crawling ants" selection rectangle around the part you've kept. Click the Select menu and chose None to get rid of those.
Note that if you go too far or otherwise screw up at any step, you can always use the Edit menu to undo what you've done, no matter what it was... even back many steps.
When you have the desired image, the next step is to rotate it clockwise by 90 degrees. Click on Image in the menu bar and hover the mouse over Transform, then click "Rotate 90 degees CW" in the submenu that appears.
Once again click on Image in the menu bar, but now hover over Mode and select Indexed. In the dialog that appears, accept the default "Generate optimum palette" with "Maximum number of colors: 256", and set "Color dithering" to "Floyd-Steinberg (normal)". Then click OK.
Note: See the Acoustic WAV_to_BMP subtopic below for special Mode and color number considerations if you plan to send and receive this image acoustically when you subsequently run WAV_to_BMP.
Finally you are ready to save the finished file. Click on File in the menu bar and select Export As. At the top of the dialog that opens, the Name item will show the name of the original image file, such as "100_1234.JPG" (if it came from a camera, for example). You must change this name to have a .BMP extension, and you may want to give it a meaningful name as well. You also need to save it in the proper folder for BMP_to_WAV to find, so click the '[+]' button next to "Browse for folders" and navigate to Documents - Daqarta - User_Data before clicking the Save button.
First of all, please note that Paint does a rather poor job at reducing a full-color photo image to 256 colors, because it does no color dithering. It just quantizes directly, leaving an image that looks somewhat cartoonish, with distinct regions of solid color. That's perfectly fine for artwork that uses solid colors, but usually not for photos of faces (for example), except maybe for impressionistic art.
But since you already have Paint, and it is simple to use, you may want to give it a try even on a photo. We'll assume here that you are starting with a large multi-megapixel photo.
Start Paint. If you are not a regular Paint user, you can find in in Start - All Programs - Accessories. (If you right-click it there you can choose Send To - Desktop to create a shortcut for future use.)
Click the File menu icon (upper left) and select Open, then navigate to your photo.
Go to File - Properties - Units and set Pixels.
Go to View and click Zoom Out until you can see the whole image.
Click Home - Resize and set Horizontal and Vertical to 25%. That's a good starting point for photos, which will take a 4000 x 3000 (12 MP) image down to 1000 x 750.
While in Home, click Select - Rectangular Selection.
Click the upper left corner of the desired region of the image and drag to the lower right to select. The cursor coordinates are shown at the lower left, while the selection dimensions are shown just to the right of that. Use the latter as a guide to selecting a 512 x 256 region. When you release the mouse button, the selection size readout vanishes and the selected rectangle remains.
If you don't like the results of your first attempt, you can just repeat the process; as soon as you click the mouse button to designate a new upper left, the original selection rectangle vanishes and you will be starting a new selection.
If a 512 x 256 selection doesn't show enough of the image, you can go back to Home - Resize for further reduction. You can go to View - Zoom In or Zoom Out as needed.
Hitting an exact selection size can be awkward. Don't worry if your selection is a little larger than 512 x 256, just don't make it smaller than that. Once you are satisfied, click on Home - Crop.
If you have overshot the 512 x 256 dimensions and want to trim to exact values, go to File - Properties and set Width and Height.
Now go to Home - Rotate - Right 90 degrees.
Finally, go to File - Save As - BMP Picture and set "256 color bitmap" in the "Save as type" drop-down, then type your chosen file name in the line above; the .BMP extension will already be filled in. Then navigate to Documents - Daqarta - User_Data and click the Save button. You'll get a prompt that says "Saving into this format may cause some loss of color information. Do you want to continue?". Click Yes and you are done.
Click File - Open and navigate to your photo. The first thing you'll note is that (unlike GIMP), the view will not be reduced to fit the screen, and in fact (unlike Paint in Vista and later) there is no way to view the whole image without actually altering it. That's OK, though, since you need to alter it anyway.
Click on the dotted rectangle at the upper right of the tool dialog so you will be ready to select rectangular regions, instead of drawing on them with the default pencil tool.
Now click on Image in the menu bar and select Stretch/Skew. In the top Stretch section of the dialog, set both Horizontal and Vertical to the same percentage. 25% is a good starting point, which will take a 4000 x 3000 (12 MP) image down to 1000 x 750.
Now click at the upper left corner of the region you want to use, and drag to the lower right. While you are doing this, the current selection size is shown at the lower right of the image area, so you can use this as a guide to selecting a 512 x 256 region. When you release the mouse button, the selection size readout vanishes and the selected rectangle remains.
If you don't like the results of your first attempt, you can just repeat the process; as soon as you click the mouse button, the original selection rectangle vanishes and you will be starting fresh.
If a 512 x 256 selection doesn't show enough of the image, you can go back to Stretch/Skew for further reduction.
Don't worry if your selection is a little larger than 512 x 256, but don't make it smaller than that. Once you are satisfied, hit CTRL+C to copy the selected region to the Windows Clipboard.
Now go to File - New and hit CTRL+V to paste your selection into the empty image area, where it will appear at the upper left corner.
At this point Paint still regards the image size as 1000 x 750 (in the above example), with the remainder being white space. Click on Image - Attributes and set Width to 512 and Height to 256 to crop to that final size, removing not only the white space but also any excess from your selection.
Next, go to Image - Flip/Rotate and select "Rotate by angle". The default selection is 90 degrees, which is what you want, so click OK.
Finally, go to File - Save As. Down at the bottom of the Save As dialog, change the "Save as type:" selection to "256 Color Bitmap" using the drop-down control. Type your chosen file name in the line above. Don't include the extension, which Paint will fill in as .BMP. Then navigate to Documents - Daqarta - User_Data and click the Save button. You'll get a prompt that says "Saving into this format may cause some loss of color information. Do you want to continue?". Click Yes and you are done.
Once you have a prepared .BMP image in Daqarta's User_Data folder, run BMP_to_WAV by hitting the F8 key followed by SHIFT plus an uppercase G, or hit CTRL+F8 to open the Macro Dialog and double-click on BMP_to_WAV in the Macro List.
You will be presented with a file Open dialog showing all the .BMP files in the User_Data folder, including the four "SpectroGramma" files included with Daqarta. Select the desired file and click Open, or just double-click the file name.
If you attempt to open a file that is not a true .BMP format, you'll get a "Not a .BMP file" message and the macro will be cancelled.
If you get an "Unsupported .BMP format" message, it means the file was a .BMP type, but used 16 colors or less, or more than 256 colors.
Otherwise, a message box will appear that gives the full path and name of the file, plus the image dimensions in pixels and the number of colors, along with the message "Writing .WAV file, please wait...". An empty black spectrogram screen will be shown, with its palette colors at the right of the display area replaced by those from the .BMP file.
The file progress is shown on the message line just above the Generator title window (above the main trace area). It will be of the form "123456 bytes written to MyFile.WAV", where the count will go up in a blur, and end at just over 1 million in about 20 seconds. (That assumes the image to be shown is 512 pixels wide, otherwise the final count and time go up proportionally.)
Once the file is saved, you can use WAV_to_BMP to play it and see the live spectrogram. However, if you are curious about the inner workings of the file, you can examine it by clicking the DD/Open button in the Daqarta toolbar and selecting that file. (Otherwise, just skip over the following section to the Operation - WAV_to_BMP subtopic.)
You'll see the color spectrogram as it would be shown during WAV_to_BMP, but without sound. Note that opening the file directly like this only shows the correct colors if you have just created it, since BMP_to_WAV sets the spectrogram palette directly from the bitmap file for this purpose. If you use DD/Open on a file created previously, the palette won't match.
Besides the image, you'll also notice some "garbage" in the first 8 columns, which represents the preamble that encodes calibration data and palette colors as sound. The final 8 columns will be missing, but you can see them by hitting the End key to show the end of the file instead of the start. (The Home key returns to the start.)
You can toggle Sgram/PT off to see the raw waveform. Hit the Home key if you have already hit End, and you will see the sync, ID, and parameter pulses. You can advance through the file via using ALT+> to move one 1024-sample frame (spectrogram column) at a time. (The file position readout below the right end of the display area shows the first sample of the current display frame.)
After the initial frame, Spectrum mode is more useful. The next frame, starting at sample 1024, will have a rectangular spectrum that represents the maximum amplitude (palette color 255, typically bright white) used for each frequency in the spectrogram column. The frequency range extends from spectral line 10 at 468.75 Hz to spectral line 265 at 12421.875 Hz, representing the 256 spectral lines used to display the 256 pixels in each column of the spectrogram. (This particular 256-line range was chosen as a compromise for modest speaker systems. See the Acoustic WAV_to_BMP subtopic for how to change that range.)
The purpose of this maximum amplitude reference spectrum is to allow on-the-fly frequency response calibration by WAV_to_BMP, as discussed under that subtopic.
The top of the rectangle is at -45.156 dB, not 0 dB. That's because 0 dB is the maximum amplitude that a .WAV file can hold at a single frequency, but here we have 256 simultaneous tones. That would normally require that the level of each be reduced to -48.16 dB or 20 * log10(1/256), but BMP_to_WAV uses a method (Newman phase algorithm) such that the tones sum to a smaller maximum waveform peak. That allows the level to be increased by about 3 dB for a better signal-to-noise ratio when using actual sound.
You can see the waveform peak by toggling Spectrum off. The highest peak is at the very start of the waveform, at sample 1024. Since this is the very first sample in the reference frame, it is adjacent to the left boundary of the display area and may be hard to see. You can use the unshifted < key to move the start of the waveform display a little earlier for easier viewing. Be sure to set it back to 1024 before toggling again to Spectrum mode, or you won't see the nice rectangular spectrum.
The next 6 frames encode the palette colors in bBgGrR order, 4 bits per frame. For example, the first of these frames (b) encodes the low 4 bits of each blue component in the palette, while the next (B) encodes the high 4 blue bits. This 4-bit encoding was used because it only requires 16 different levels, compared to 256 levels for a full 8-bit encoding. That means that sound levels can be kept higher to reduce the influence of noise while acquiring the critical palette data. (This is only an issue when WAV_to_BMP uses an Input channel, since acoustic data is typically quite noisy.)
Finally, starting at sample 8192, are the frames of bitmap data. Each frame consists of 1024 samples containing 256 simultaneous tones, the amplitudes of which encode the palette color numbers for the corresponding pixels in one column of the spectrogram.
After running BMP_to_WAV to turn your .BMP image into a .WAV file in Daqarta's User_Data folder, you can play (hear and see) that file with the WAV_to_BMP macro.
Note that by default WAV_to_BMP assumes you simply want to hear the sound as well as see the resulting image via the Daqarta Spectrogram. That is by far the simplest approach, and will work even if the trial period expires and you have not purchased a Daqarta license. You'll see Daqarta's output Volume dialog, plus a message box that says "Unmute and adjust Volume for comfortable listening. Then hit 'w' key to select .WAV file. (Any other restores default palette and exits.)".
Sound output is muted here by default, so if you only want to see the spectrogram image you can just hit the 'w' key. Otherwise, when you unmute the volume you will hear a 440 Hz tone while you adjust the volume as desired.
Alternatively, if you want to try your hand at sending and receiving the image acoustically, you should make sure the Input button is active before you run WAV_to_BMP. Note that getting a decent image will be a serious challenge for your overall sound system... see the Acoustic WAV_to_BMP subtopic below for more infomation.
In this case, besides the output volume dialog, you'll also see the Input controls dialog. The message will be "Unmute and adjust Volume and Input Range for large undistorted yellow Input signal. Then hit 'w' key to select .WAV file. (Any other restores default palette and exits.)".
When you unmute the output volume, you'll hear the 440 Hz tone as well as see its Input spectrum, which ideally should have a single peak near the left end of the display. You may see a whole forest of spikes, which indicates excessive distortion. In this case, reduce the output volume and/or set a lower (more-negative) Input level until the extra spikes just go away. Otherwise, if you don't see the distortion peaks, raise the volume and/or Input level until you just see them, then back off until they go away.
In either case, once you hit the 'w' key you'll see the spectrum of the "preamble" sound waveform. At this point, the macro has loaded the WAV2BMP.GEN Generator setup, and into that has loaded the selected .WAV file as a Play waveform source. The macro now proceeds to play each of the first eight 1024-sample preamble frames multiple times while calibrating the system.
The initial screen will show the spectrum of the biphasic pulses used for sync, ID, and parameters. Actually, by the time this spectrum appears, the ID and parameter pulses have already been analyzed directly from the digital waveform, so the only thing this "live" signal is used for is the initial sync pulse at the start of the frame, and that is analyzed as the raw waveform from which the spectrum is derived. The sync pulse is used to adjust the Trigger Delay to compensate for acoustic delay between output and input if you are using acoustic input mode, but that adjustment doesn't hurt anything for output-only mode.
Then the rectangular full-scale reference spectrum appears, and is averaged while being repeated 32 times. This is to reduce the influence of noise in case this is an acoustic input signal. If it is, then the next step will be to create a mirror-image spectrum curve to compensate for the overall frequency response of the system, including speakers and microphone. (This step isn't used for output-only operation, since the direct digital signal has no response problems to correct.)
Then each of the 6 palette frames is repeated 32 times and averaged as above, to decode the 4-bit bBgGrR data from each frame into the 24 bit RGB values describing the 256-color palette. Again, this averaging isn't really needed for output-only operation, but for acoustic inputs it helps insure that the palette colors are not corrupted by noise. The decoded palette is then set for Spectrogram mode.
Finally, the macro sets the Play From and Play To parameters to omit the preamble and just play the remainder of the .WAV file repeatedly. Spectrogram mode is activated and the dB range of the palette is set based on the preamble parameters. The image slowly scrolls into view from right to left and repeats indefinitely. Hit Pause (ALT+P) if you want to freeze the image at any time; sound output is not paused.
The sound you hear is being used to create the spectrogram in real time. The frequency range extends above 12 kHz when the default 48000 Hz sample rate is used. In output-only mode, you can reduce the sample rate during playback to lower the pitch into a more audible range. This has no effect on image quality, only the speed at which the image appears.
In acoustic input mode, however, this will mess up the automatic speaker/microphone frequency response compensation that was done at the start. You'll need to re-run WAV_to_BMP with the new sample rate in order to get a new response correction.
As noted under the WAV_to_BMP subtopic above, that macro assumes you simply want to hear the sound as well as see the resulting image via the Daqarta Spectrogram. That approach doesn't use the sound card Input; it just plays the sound via the Generator, and displays the spectrogram of the raw waveform data that is being sent to the sound card output. This gives essentially perfect reproduction of the original .BMP image.
It is vastly more challenging to get a decent image from the acoustic waveform that has been generated by a sound system and monitored by a microphone via the sound card Input. This is not only a test of your sound system, microphone, and sound card, but also of your troubleshooting abilities and your ingenuity.
Note: The Duplex Delay settings for your sound card must have been previously determined by the Auto-Calibration process. This compensates for inherent sound card time lags between output and input; WAV_to_BMP can then properly compensate for acoustic lags by using the sync pulse at the start of the "sound preamble" in the .WAV file to adjust the Trigger Delay. This is needed to insure that 1024-sample "frames" of the acquired waveform are aligned with the starts of spectrogram (and hence .BMP image) columns.
The whole concept of BMP_to_WAV and WAV_to_BMP is to store and later recover a short 1024-sample tone for each pixel of the bitmap. 256 simultaneous tones are used for the 256 pixels in each column of the image. The frequency of each tone encodes the pixel's vertical position, the time of the tone start encodes the horizontal, and the loudness encodes the color. Getting the specified frequency presents no problem, as long as the frame is properly synchronized... which also insures the proper horizontal position. The weakest link is measuring the correct loudness for each and every tone.
The overall frequency response must be absolutely flat, since errors of a fraction of a dB at any frequency can cause the wrong color to be rendered at that frequency, which corresponds to a given horizontal line on the spectrogram.
For example, if the response is down by 0.12 dB at a certain frequency (using the default parameters with 256 colors), the spectrogram will show the next-lower palette color from the original. In a .BMP color palette, colors have little (if any) correlation with their neighbors, so there will be a glaring discrepancy. And since this is an overall frequency response problem, every pixel in that row (same frequency) will be off by the same amount. Furthermore, since the original image pixels in that row are typically different colors, you won't see a streak of a certain wrong color, but a streak of "wrongness" having apparently random colors.
The same thing happens at every frequency, so without a perfect frequency response you get a perfect mess.
WAV_to_BMP attempts to deal with this automatically by using the preamble reference spectrum that was saved by BMP_to_WAV. It's a 256-tone sound that has all frequencies at the same maximum level, which would otherwise be displayed as the top color in the spectrogram palette. If the overall sound system had perfect frequency response (at least over the chosen 12 kHz range used here), the measured spectrum would be perfectly flat. WAV_to_BMP measures the deviation at each frequency and creates a mirror spectrum. For example, if the measured response is -1 dB at some point, the mirror will be +1 dB at that point. This mirror is then installed as a Spectrum Curve that perfectly flattens the overall response.
But there are definite limits to this approach. First, if there is a deep dip in the speaker response, or steep roll-off near the high or low ends, the softer sound will have a greater percentage of contaminating background noise. The flattened signal will thus have larger errors in these regions. Second, the slopes and locations of sharp dips and peaks can vary over time due to changes in the temperature of the air and the mechanical properties of the speaker components. But the Spectrum Curve is only created once; it doesn't adapt.
Another big problem is distortion, especially by the speaker but also by the microphone, and possibly even by the sound card itself. With the default sound level range of 31 dB, the overall sound system would require distortion of less than 3 percent to prevent the loudest tones from contaminating the softest. That's pretty easily exceeded by most woofers just for simple harmonic distortion of single loud tones, but here we can have many high-level tones at once (bright pixels in the same column), causing special concern for intermodulation distortion. BMP_to_WAV uses a 12 kHz frequency range starting at 468.75 Hz to avoid woofer distortion. (Midrange and tweeter drivers tend to have less problems in this department, since their diaphragms don't have to move as much to generate the same sound levels.)
One way to reduce distortion is to reduce the overall sound level, but unfortunately this brings the signal closer to the background noise. When you start WAV_to_BMP with the Input active, you are prompted to adjust the output volume and the input range while monitoring the spectrum of a 440 Hz reference tone.
The goal here is to get the loudest possible sound with the least distortion. Another way to put this is that you want the biggest difference between the 440 Hz peak and everything else. Once you reduce the distortion peaks into the noise background, further level reductions will be counter-productive, reducing the signal relative to the constant-level noise.
Other acoustic problems can arise from room reverberation, which in this application is just as much a problem as background noise.
A related but less serious issue is multiple sound paths from different speaker drivers. Ideally, you want all the sound for one vertical spectrogram column to arrive pretty much at the same time, not with some components arriving later due to different acoustic delays. For this reason, when Input is active at the start of WAV_To_BMP the Generator output is switched to Left Solo mode. This means only the Left output channel will be active; the Right channel will receive only null data for silence. (Selecting only the Left Generator output enable without Solo would give mono mode, which sends the Left signal to both outputs.) So, make sure your microphone is aimed at the Left speaker.
Noise, distortion, and frequency response changes will all contribute to deviations in the received sound level at any given pixel frequency. As noted earlier, tiny level differences can cause big color differences. With the default settings, BMP_to_WAV encodes 256 levels (one for each palette color) into a 31 dB range, leaving only 0.12 dB between colors. If we could increase that spacing, the process would be less sensitive to small level errors.
One way would be to increase the dB range in use. The problem is that due to the presence of 256 simultaneous tones, the maximum level of each has to be about 45 dB lower than the full-scale tone that could be used in a single-tone application. So if we increase the range, it has to be to make the softest tones softer. If the softest tone (bottom palette color) is 31 dB below the -45 dB peaks, it is around -76 dB relative to full scale. That's pretty soft, and likely to be near the background acoustic noise level. Thus, increasing the available range has a cost in terms of signal to noise ratio.
An alternative way to get a larger dB spacing between colors is to use less colors over the same loudness range. Although the Daqarta Spectrogram always uses a palette of 256 colors, there is no reason they have to be different colors. BMP_to_WAV and WAV_to_BMP detect the number of colors actually used by the image; if that number is 128 or less, the palette colors are "doubled up" so that the color bars in the palette (as shown on the right edge of the spectrogram) are twice as wide. With only 64 colors, they are 4 times as wide, and with 32 colors they are 8 times as wide.
That means the process is less sensitive to noise, distortion, and other errors, but of course it means the target image quality is reduced by using the smaller palette. Nevertheless, this controlled degradation is usually much more acceptable than random color speckles and other corruption due to error sensitivity.
To prepare an image with less colors, you will need to use GIMP or similar software... Windows Paint can't do it. Refer to the Image Preparation Using GIMP subtopic above. Where you select Image - Mode - Indexed, change the "Maximum number of colors: 256" to 128, 64, or 32 and proceed as before.
Alternatively, instead of choosing Image - Mode - Indexed, you can use Image - Mode - Grayscale. Although you will lose the colors, a black and white image is much less sensitive to errors. The reason for this is discussed in Basic Theory.
The default 31 dB loudness range can be adjusted by changing the QR parameter at the start of BMP_to_WAV. A smaller range means the colors are closer together, but the tones stay louder. This keeps them farther above the background noise, but since they are closer together they are more sensitive to that noise by the same amount; there is no net improvement in image quality.
Another way that might improve image quality is to change the frequency range of the sound to be a better match to your speaker response.
You can adjust the frequency range in two ways. The first is by changing the U0 parameter from its default of 10. That sets the lowest tone frequency according to SmplRate / 1024 * U0. With the default rate of 48000 Hz each step of U0 raises that frequency by 46.875 Hz; with U0=10 it is 468.75 Hz. The highest frequency will be 255 steps higher than U0, or 12421.875 Hz.
The other way to change the frequency range is by changing the sample rate. If you cut it in half to 24000 Hz, the default U0 will give a range from 234.375 to 6210.9375 Hz. Cutting to 12000 Hz will give 117.1875 to 3105.46875 Hz.
You don't need to re-run BMP_to_WAV when you change the sample rate, only if you change U0. The .WAV file can be played back at any sample rate, as long as you set that rate before you run WAV_to_BMP.
Note that each time you cut the sample rate in half, the time to display the image doubles.
When you run either BMP_to_WAV or WAV_to_BMP the Spectrogram palette is replaced with that of the .BMP image. You can quickly restore Daqarta's default palette by running WAV_to_BMP and hitting any key other than 'w' when prompted. That restores the Default.PAL before cancelling the macro.
(The same thing happens with BMP_to_WAV if you cancel the file open dialog, or attempt to load a non-BMP file.)
;<Help=H491E QR=31 ;0-31 dB bitmap range QP=30 ;0-30 dB palette nybble range U0=10 ;Bottom spectral line (468.75 Hz) Close= ;Close any open file UserUnits=0 ;No User Units Gen=0 ;Generator off E.IF.Input= ;Input enabled? InL=1 ;Stereo Input for later DD/Open InR=1 Input=0 ;Input off ENDIF. Sgram=1 ;Empty Sgram on PitchTrk=0 ;Pitch Track off XpandMin=U0 * SmplRate / 1024 ;Set Sgram freq range XpandMax=(255 + U0) * SmplRate / 1024 Xpand=1 ;Show only above range Msg="<D(60,60)" ;Set Msg position ;Get file and verify format: Buf0#N=1 ;Read 1 channel Buf0#W=16 ;16-bit data Buf0#H=0 ;No header Buf0="<LoadDAT:*.BMP" ;Open BMP file IF.Buf0=!h4D42 ;'BM' for BitMap file? Msg="Not a .BMP file." A.LoadPAL="Default" ;Restore default Sgram palette Cancel= ;Exit macro if not ENDIF. UF=0 ;No format errors found yet IF.Buf0=!40 ;BITMAPINFO header size test UF=1 ;Set error flag if wrong size ENDIF. IF.Buf0=!1 ;Bit Planes = 1 ? UF=1 ENDIF. IF.Buf0=!8 ;8 bits/pixel (256-color indexed) ? UF=1 ENDIF. IF.(Buf0+Buf0)=!0 ;Compression = 0 ? UF=1 ENDIF. IF.UF=1 ;Any format errors? Msg="Unsupported .BMP format." Cancel= ENDIF. QW=Buf0 ;Bitmap width in pixels Qw=QW ;Assumed Sgram height IF.Qw=>256 ;Limit to 256 Qw=256 ENDIF. QH=abs(Buf0) ;Height in pixels (neg = top down) GetFilePath=1 ;Copy file path ;Re-open file to read palette data: UO=Buf0 ;Offset to bitmap Buf0#H=UO - 1024 ;Offset to palette, skip as 'header' Buf0#N=1 ;Read 1 channel Buf0#W=32 ;32-bit data A.Buf0="<LoadDAT:" + FileName ;Re-open BMP file with new params ;Determine number of active palette colors: UI=255 ;Max palette index WHILE.UI=>0 ;Work from top down IF.Buf0[UI]=>0 ;Look for first non-zero entry LoopBreak=2 ;Found highest entry in use ENDIF. UI=UI-1 ;Else next-lower palette entry WEND. QN=UI+1 ;1-based palette size = top + 1 Msg=FileName +n +n + QW + " x " + QH + " pixels, " _ +QN + " colors." _ +n +n + "Writing .WAV file, please wait... " ;Widen palette bars if 128 colors or less: Buf1="<=B0" ;Palette copy for widening Buf3="<=B0" ;Copy for WAV preamble QJ=int(256 / QN) ;Number of adjacent duplicates UI=0 WHILE.UI=<QN ;Do all colors C=Buf1[UI] ;Get RGB color UJ=0 WHILE.UJ=<QJ ;Create QJ adjacent copies in Buf0 Buf0[QJ*UI + UJ]=C UJ=UJ+1 WEND. UI=UI+1 WEND. Buf0#Ps=0 ;Set palette for Sgram Sgram=1 ;Re-activate to show new palette at right ;Set dB value for each palette color, store in Buf2: K=32767 ;Full-scale WAV amplitude UI=0 WHILE.UI=<QN ;Do all colors D=UI * (QR / (QN-1)) - QR ;Bottom color = -R, top = 0 dB Buf2[UI]=exp10(D / 20) * K ;Convert dB to amplitude UI=UI+1 WEND. ;Pre-compute Newman phases in Buf4 for each pixel freq. ;(Reduces waveform peaks to allow higher levels.) Buf4="<=(0)" ;Clear Buf4 UI=U0 ;Initial index UN=256 + U0 ;Limit index WHILE.UI=<UN T=tan(pi * (UI-1)^2 / UN) X=1 / sqrt(1 + T^2) Buf4[2*UI]=X ;Re part Buf4[2*UI + 1]=X * T ;Im part UI=UI+1 WEND. ;Data for "preamble" sync/info waveform frame: Buf0=hB2AF ;ID=BMP 2 Audio Format Buf0=QN ;Number of colors Buf0=QR ;Bitmap dB range Buf0=U0 ;Bottom spectral line Buf0=QP ;Palette nybble dB range L=1024 ;0, 1K, 2K, ... 15K output levels ;Create sync pulse: Buf1="<=(0)" ;Clear output wave Buf1 UN=0 ;Waveform Buf1 index A=20 * L ;20K initial pulse larger than rest WHILE.UN=<4 ;Biphasic sync pulse, 4 samples/phase Buf1[UN]=A ;Pos phase Buf1[UN+4]=-A ;Neg phase 4 samples later A=0.9 * A ;Decay so peak = leading edge UN=UN+1 ;Next sample WEND. ;Save ID and info data in preamble waveform frame: UN=UN+4 ;Start of ID data UI=0 ;Value (word) index WHILE.UI=<5 ;Send 5 data words A=Buf0[UI] ;Get 16-bit data to send UJ=0 ;Nybble counter WHILE.UJ=<4 ;Send 16 bits as 4 nybbles, low to high UA=A & 15 ;Send only low nybble UK=0 ;Sample counter WHILE.UK=<4 ;4 samples per phase Buf1[UN+UK]=UA * L ;Pos phase Buf1[UN+UK+4]=-UA * L ;Neg phase UK=UK+1 ;Next sample in nybble WEND. UN=UN+8 ;Start sample of next nybble A=A >> 4 ;Shift down by one nybble UJ=UJ+1 ;Next nybble WEND. UI=UI+1 ;Next data value WEND. A.Buf1="<SaveWAV:" + FileName?P ;Save waveform frame ;Send 0 dB reference frame, all spectral lines (1 pixel column): Buf1="<=(0)" UI=U0 ;Start spectral line index US=1 D=(QN-1) * (QR / (QN-1)) - QR ;Max pixel color = 0 dB D=exp10(D / 20) * K ;Convert dB to amplitude WHILE.UI=<Qw + U0 ;Do all spectral lines Buf1[2*UI]=D ;Re part Buf1[2*UI+1]=D ;Im part UI=UI+1 WEND. @_IFFT_Buf1_WAV ;Take IFFT and append to WAV file ;Send RGB palette as 6 frames, lo-hi nybbles (bBgGrR): ;Spectral lines hold one nybble for each color in the palette. US=0 WHILE.US=<(4 * 6) ;6 frames, 4 bit shifts each Buf1="<=(0)" ;Clear output wave buffer UI=0 WHILE.UI=<QN ;Do all colors D=((Buf3[UI] >> US) & 15) * QP/15 - QP ;0-15 nybble = 0-30 dB D=exp10(D / 20) * K ;Convert dB to amplitude Buf1[2*(UI+U0)]=D ;Re part Buf1[2*(UI+U0)+1]=D ;Im part UI=UI+1 WEND. @_IFFT_Buf1_WAV ;Take IFFT and append to WAV file US=US + 4 ;Next nybble shift amount WEND. ;Send BMP pixel values as spectral lines: Buf0#H=UO ;Offset to bitmap Buf0#WU=8 ;8 bits per pixel, unsigned UW=4 * ceil(QW/4) ;BMP rows are multiples of 4 bytes UR=0 WHILE.UR=<QH ;Do all columns Buf0#L=UW * (QH-1 - UR) ;Set file posn Buf0#N=1 ;1 channel A.Buf0="<LoadDAT:" + FileName ;Pixel column (indexes 0-255) Buf1="<=(0)" ;Clear output wave buffer UI=U0 ;Start spectral line index WHILE.UI=<Qw + U0 ;Do all spectral lines UX=Buf0[UI-U0] ;Byte value 0-255 D=Buf2[UX] ;Get dB value Buf1[2*UI]=D ;Re part Buf1[2*UI+1]=D ;Im part UI=UI+1 ;Next spectral line WEND. @_IFFT_Buf1_WAV ;Take IFFT and append to WAV file UR=UR+1 ;Next column WEND. ;Set Spectrogram range for file viewing: D=-45.156 ;'0 dB' ref spectrum level G=20 * log10(Range?0) ;Input range dB for later DD/Open S=(QR+1) / QN ;dB per palette color YlogScrnTop=D + G + S/2 ;Set Sgram range YlogScrnBot=D + G - QR + S/4 DDiskReadStep=1024 ;File read samples per Sgram column Msg= ;Remove message
;<Help=H491E ;On entry, Buf1 = complex spectrum, Buf4 = phase factors Buf1="<*B4)" ;Apply phase factors Buf1="<*(65536)" ;Scaling for IFFT Buf1="<iB1" ;IFFT to get waveform Buf1="<*(1.414 / 16384)" ;Scaling for WAV range Buf1#N=1 ;1 channel A.Buf1="<SAveWAV:" + FileName?P ;Append to WAV file
;<Help=H491E UC=2 ;Assume output-only (Left Out chan) IF.Input=1 ;Unless Input already active InL=1 ;Stereo Input InR=1 ;Right Input on, but ignored RI.Disp=0 ;Right In display off UC=0 ;Left In chan ENDIF. T=100m ;Settling time after changes Close= ;Close any open file UserUnits=0 ;No User Units SmplSec=0 ;Trigger units in samples TrigDelay=-100 ;Default, will auto-adjust TrigMode=GenSync ;Sync to Gen frame rate Trig=1 ;Trigger on Spect=1 ;Show Spectrum SpectWindOn=0 ;No window function PSD=0 ;No PSD Ylog=1 ;Ylog (dB) axis YlogScrnTop=6 ;+6 to -90 dB axis YlogScrnBot=-90 SpavgMode=Lin ;Use linear Spectrum average SpavgFrames=32 ;32 frames (change as needed) A.CurveFile0=0 ;No Spectrum Curve 0 A.LoadGEN="WAV2BMP" ;Load Generator setup Gen=1 ;Generator on GenVolDlg=1 ;Open Volume slider dialog Msg="<D(60,60)" ;Set Msg position IF.UC=<2 ;If Input active: GenSoloL=1 ;Left Out only InputDlg=1 ;Open Input dialog Input=1 ;Restart Input (closed by Gen load) Msg="Unmute and adjust Volume and Input Range" _ +n + "for large undistorted yellow Input signal." _ +n + "Then hit 'w' key to select .WAV file." _ +n + "(Any other restores default palette and exits.)" WaitKey=128 ;Wait for any key GenVolDlg=0 ;Close volume slider dialog InputDlg=0 ;Close input dialog ELSE. ;If output only: Msg="Unmute and adjust Volume for comfortable listening." _ +n + "Then hit 'w' key to select .WAV file." _ +n + "(Any other restores default palette and exits.)" WaitKey=128 ;Wait for any key ENDIF. QM=MasterMute ;Save Mute state Msg= ;Close message Gen=0 ;Generator off temporarily IF.Key?#=!"w" ;Not 'w' key? A.LoadPAL="Default" ;Restore default Sgram palette Cancel= ;Exit macro ENDIF. PrefVolChg#X=0 ;No exit prompt for above volume changes L.0.Play0="*.WAV" ;Select .WAV file for Play0 ;Get params from first 1024 samples of WAV file: Buf0="<=P0" ;Copy start of Play0 waveform to Buf0 Buf1="<=(0)" ;Clear Buf1 U1=0 ;Buf1 parameter index U0=8 ;Buf0 sample index, skip sync pulse WHILE.U1=<5 ;Get 5 param words UN=0 ;Nybble index UW=0 ;Word value WHILE.UN=<4 ;4 nybbles per param word UA=0 ;Average nybble value UI=0 ;Nybble sample index WHILE.UI=<4 ;4 samples per pos pulse phase UA=UA+Buf0[U0+UI] ;Add pos samples to total UI=UI+1 ;Next nybble sample WEND. WHILE.UI=<8 ;4 more samples per neg phase UA=UA-Buf0[U0+UI] ;Subtract neg samples from total UI=UI+1 ;Next nybble sample WEND. U0=U0+8 ;Sample at start of next nybble (pos) UW=UW + abs(UA) / (8 * 1024) << (4 * UN) ;Nybble to word UN=UN+1 ;Next nybble WEND. ;Get all 4 nybbles Buf1[U1]=UW ;Save param word U1=U1+1 ;Next param word WEND. ;Get all 4 params IF.Buf1(0)=!hB2AF ;ID param = "BMP 2 Audio Format"? Msg="Not a BMP_to_WAV file." Cancel= ;Exit if not ENDIF. ;Get parameters from Buf1: QN=Buf1 ;Number of palette colors QR=Buf1 ;Bitmap dB range = 0 to -QR S=(QR+1) / QN ;dB per step U0=Buf1 ;Bottom spectral line # QP=Buf1 ;Palette nybble dB range ;Use initial sync pulse to adjust timing: L.0.PlayFrom=0 ;Play 1st frame repeatedly L.0.PlayTo=1023 Gen=1 ;Output on IF.UC=<2 ;If Input active at start, Input=1 ;Input on also ENDIF. WaitSecs=500m ;Wait for sound output Buf0="<=W(UC)" ;Capture waveform P=Buf0?p ;Scan for pos peak (ignore amplitude P) A=Posn?p ;Position of pos peak P=Buf0?n ;Scan for neg peak B=Posn?p ;Position of neg peak IF.B=<A ;If neg before pos, polarity inverted A=B ;A = leading edge ENDIF. TrigDelay=TrigDelay + A ;Adjust so leading edge is frame start ;Get averaged 0 dB reference spectrum: L.0.PlayTo=2047 ;Play second frame L.0.PlayFrom=1024 WaitSecs=T ;Wait for sound start Ch=UC ;Set channel (0=Left In, 2=Left Out) Ch.SpectCurve=0 ;Cancel any Spectrum Curve on channel K=log10(2) ;Constant for Buf log10 Avg=1 ;Start spectrum average WaitAvg= ;Wait for average to finish D=sSig(U0,255+U0) / 32767 ;Spectrum sigma over band D=20 * log10(D) - 10 * log10(256) ;Avg dB in band Buf0="<=(0)" ;Clear Buf0 for spectrum correction IF.UC=<2 ;If Input, create response correction curve Buf0="<=A(UC)" ;Copy averaged spectrum (magnitude) Buf0="<*((2^(-15)) / 32767)" ;Apply scaling Buf0="<l(K)" ;Log10 of spectrum Buf0="<*(20)" ;Spectrum in dB Buf0="<~(D)" ;Reflect curve about average value Buf0="<o(U0,(255+U0))" ;Crop to keep only active range Buf0="<uV0" ;Upload as Memory Curve 0 Ch.Curve0=1 ;Apply Curve0 to Input channel ENDIF. Pause=0 ;UnPause after prior average ;Recover the palette from 6 nybble frames in bBgGrR order: Buf2="<=(0)" ;Clear palette working copy UP=0 ;Nybble frame counter WHILE.UP=<6 ;Do all 6 frames L.0.PlayTo=3071 + UP * 1024 ;End of nybble frame L.0.PlayFrom=2048 + UP * 1024 ;Start of frame WaitSecs=T ;Wait for sound start Avg=1 ;Start spectrum average WaitAvg= ;Wait for average to finish Buf1="<=A(UC)" ;Copy averaged spectrum (magnitude) Buf1="<*((2^(-15)) / 32767)" ;Apply scaling Buf1="<l(K)" ;Log10 of spectrum Buf1="<*(20)" ;Spectrum in dB Buf1="<+B0" ;Add Buf0 to correct to flat spectrum Buf1="<-(D - QP)" ;0 dB becomes +QP (+30) value Buf1="</(QP/15)" ;Nybble value (0 dB = +15) UI=0 ;Palette color counter WHILE.UI=<QN ;For all colors, apply nybble to dword Buf2[UI]=Buf2[UI] + cint(Buf1[UI+U0]) << (4 * UP) UI=UI+1 ;Next palette color WEND. Pause=0 ;UnPause from above average UP=UP+1 ;Next nybble frame WEND. ;Widen palette bars if 128 colors or less: QJ=int(256 / QN) ;Width multiplier UI=0 ;Palette color counter WHILE.UI=<QN ;Do all colors C=Buf2[UI] ;Get palette color UJ=0 WHILE.UJ=<QJ ;Do each width multiple Buf0[QJ*UI + UJ]=C ;Color to final palette UJ=UJ+1 ;Next width multiple WEND. UI=UI+1 ;Next palette color WEND. Buf0#Ps=0 ;Set palette for Sgram ;Set Generator to play image portion of .WAV as Sgram: L.0.PlayTo=1G ;Set end too large, limits as needed L.0.PlayFrom=8192 ;Start of image portion MasterMute=QM ;Unmute sound output as needed SgMode=Scroll ;Set Sgram for scroll mode Sgram=1 ;Start Spectrogram PitchTrk=0 ;No Pitch Track XpandMin=U0 * SmplRate / 1024 ;Set active freq range XpandMax=(255 + U0) * SmplRate / 1024 Xpand=1 ;Show only active range G=20 * log10(Range?V) ;Get total gain of L.In or L.Out YlogScrnTop=D + G + S/2 ;Set Sgram top color dB YlogScrnBot=D + G - QR + S/4 ;Bottom color dB
See also Macro Examples and Mini-Apps
Questions? Comments? Contact us!We respond to ALL inquiries, typically within 24 hrs.
Over 30 Years of Innovative Instrumentation
© Copyright 2007 - 2017 by Interstellar Research
All rights reserved