Quick post today. I recently stumbled across this excellent PDF by Nick Golas. It contains an enormous quantity of time-saving tips for users of LabVIEW (originally hosted by the IEEE here). What’s great about this particular work is that it’s illustrated with a ton of screenshots. For instance, I did not know that you could use the mouse scroll wheel, along with the Ctrl key, to quickly flip through stacked structures:
I’ve also never noticed the “Retain Wire Values” toolbar button, which does just that: it retains the last values of any wires which have been executed. You can then apply probes while the VI is stopped and view the last states of your program.
Seriously though, if you use LabVIEW, take a few minutes and check this out.
Like many people, I have fairly diverse taste in music. My media library holds tracks from Bach, Beethoven, Billy Joel, Bonobo, Brent Lamb, Brahms, Brian Hughes, and the Bee Gees (to name just the “B” section). I love variety. The trouble is, all of these different genres tend to require slightly different volume settings. Worse still, in the case of some classical music, you can get quite a wide range of volumes within a single piece (e.g. O Fortuna). So if I hook up my BlackBerry and set it to shuffle, I find myself having to continually adjust the volume knob – either because I can hardly hear the current track, or because my neighbors are about to come banging on my door.
Well, I’m not the first one to have this problem. Nor am I the only one to attempt to solve it. In fact, it’s already been solved. As you may know, there are plenty of software solutions out there for so-called volume leveling. But before the advent of the BlackBerry, or the personal computer, there was the analog compressor.
The sole purpose of such a device is to compress the dynamic range of an incoming audio signal: amplifying the soft parts and reducing the volume of the loud parts (thus decreasing, or compressing, the range of the track’s volume). It’s quite a simple thing, really. And you can buy one of these units for between $100 and $200. But why buy something when you can build it from spare parts? 🙂
Now for all of you comp-sci majors out there who are planning on commenting on any one of a million different audio leveling programs out there, don’t worry, I know they exist. The trouble is, even if I leveled every single MP3 I possess, what about internet radio? Specifically, the Pandora app for my BlackBerry? Alright, maybe there’s a software-only solution for that too (although I haven’t found it), but you know what? Just for the fun of it, I’m going with hardware this time. Well, mostly. Actually, it’s going to be a mix of analog hardware and an 8-bit Atmel AVR running some embedded C code. Sound good? Alright, then let’s get started!
First Prototype: Powered by LabVIEW
As frequent readers of my blog will know, I’m a big fan of the LabVIEW programming language. I can use it to whip up fairly complex data acquisition and processing applications in a matter of minutes (examples). Much faster than writing code for an MCU. So I decided to use it, along with my handy Mobile Studio IOBoard and Bus Pirate, for my first compressor prototype. Before we get into the brains though, let’s talk about the guts (and by that I mean let’s talk about the analog circuitry which will be common between the LabVIEW and the MCU prototypes).
First off, we need some way of detecting the volume of our incoming audio signal. To do this, you could simply connect your left and right audio channels directly to your ADC inputs. Two problems with this though. First, unless you’ve really cranked up your source’s volume, you may only see peak signals of around 100mV. Such small voltages will mean poor resolution. In other words, without some type of amplification, we won’t be able to detect fine changes in the input’s volume.
To counter this, I have added two operational amplifiers (op-amps, U1) which connect, via high-pass filters, to our incoming left and right audio signals. Each op-amp increases the amplitude of its respective signal by about 50x (click to enlarge):
The purpose of those two AC-coupling high-pass filters is to remove any DC offset from the input signals. Without these filters, we would also be amplifying that offset, such that even a 0.1V bias would saturate this first set of op-amps (which are supplied by +5V and -5V).
Once amplified, there is one more thing we need to consider. Since audio waveforms change rapidly, and swing between positive and negative voltages, our input signal may appear to change wildly between ADC samples. What we need then is a circuit that can smoothly follow the peak amplitude of our input signal. Kindof like an old VU meter.
The amplified input audio signal is shown above in blue, while the output from our “peak hold” circuit is shown in green (this plot displays 1V/div vertically, 50ms/div horizontally). Just how does this work, you ask? Well, scroll back up to my earlier schematic. The key is the two sets of diodes (D1 and D2) and 10uF capacitors (C3 and C4) you see just past the first amplifier stage. Diodes only allow current to flow in one direction (from left to right, in this case). Thus, when the left channel amplifier’s output is greater than the voltage across C4 plus 0.35V (due to the voltage drop across the diode), C4 will be charged to a higher voltage. Once the op-amp’s output drops, the diode D2 prevents C4 from discharging. Thus, it “holds” the op-amp’s peak voltage. However, because we don’t want C4 to hold this voltage indefinitely, I’ve added resistor R15 (R14 on the right channel), which gives C4 a path through which it may slowly (kindof) discharge. The result is the scope plot above.
Now at this point, I could’ve stopped and wired both peak voltage outputs directly to my ADCs. However, I would then have needed to read and average two voltages within my leveling program. Since I’m on a hardware kick, I decided to simply sum the two using an inverting summing amplifier configuration (R7, R8, R9, U2). Because this circuit’s output is inverted (negated), I then passed the signal through a unity gain (1x), inverting amplifier (R10, R11, U2). Now we’re ready for measurements! By the way, in case you have as much trouble as I do remembering what all of these op-amp circuits look like, you should download or bookmark this nifty guide from TI. I use it all the time.
Before I move onto the brains of the device, I should explain how I intend to digitally control volume. Recently, I acquired a Bus Pirate and a few SPI digital potentiometers. My plan here is to use these potentiometers (P/N MCP42010), as 256-position voltage dividers. They will be wired in-line with each audio channel. You can see one of them (one IC containing two pots) shown as a green box in my earlier schematic. The output from each pot will be fed into a buffer op-amp in order to keep the outputs strong (capable of driving headphones or line inputs). For my initial LabVIEW prototype, I will be communicating with the potentiometers via an RS232 connection to the bus pirate.
Alright, so onto the brains! [Insert zombie joke here] The image above is a screenshot of my VI (virtual instrument, also known as a LabVIEW program). As you might expect, it is quite simple. I have a control for the potentiometers’ wiper positions, as well as indicators for the instantaneous and filtered ADC inputs. The analog input is being provided by my old friend, the RPI IOBoard. Its interface consists of just a few blocks on the diagram (for those of you unfamiliar with LabVIEW, what you see here is equivalent to written code):
I have divided the program into two while loops (the large grey-border rectangles), each running at a 50ms rate. The top loop handles communication with the bus pirate (initial bus pirate configuration I perform once, by hand, through the terminal). This communication is quite simple, and consists of just two bytes. The first byte is a command which tells the potentiometers to write the second byte to their wiper position registers.
The bottom loop is responsible for determining the appropriate wiper position, based on the incoming voltage measurements. My logic is quite simple. Perhaps you can determine it from the diagram alone? Basically, I compute a difference between the current (instantaneous) voltage measurement and our last filtered value. Then, if the current measurement is greater than the filtered measurement, I divide the difference by 40 and add it to the filtered measurement. If the current measurement is less than the filtered measurement, I divide the difference (which is negative) by 240 and add it to the filtered measurement. The result of this is that for loud sounds, the filtered measurement increases fairly quickly (within a second or two). However, if the input volume drops, the filter will take perhaps five times longer to respond.
Once the filtered measurement has been computed, the appropriate wiper position is determined via a look-up table. That table, when plotted, looks something like this:
These values were determined empirically. I simply listened to the circuit’s output for various inputs and adjusted the wiper position manually to achieve the best sound. However, as you can see, it implements a log(x) function, as you might expect with an audio signal. But rather than attempting to determine a precise formula for this function, as I am designing this for use on an 8-bit MCU, I chose the look-up table approach.
With my VI wired and ready to go, I proceeded to tune my filtering algorithm to provide leveling over the whole musical spectrum. The results sounded pretty good. But before I demonstrate anything, let’s talk about the final implementation using the ATMega328.
Second Prototype: Powered by Atmel
For my embedded prototype, I again chose an AVR microcontroller. I’ve long been a fan of these chips. They’re just so easy to work with, and quite powerful. Implementing the SPI interface required just a few simple commands, since the protocol is natively supported by the ATMega’s hardware. This particular chip also has a built-in, six-channel ADC, so that was no big deal either. And since I’d already developed the algorithm and settings in LabVIEW, the most time-consuming part of this whole process was wiring up this lovely 10-segment LED bar graph. Now I need more resistors…
Although I would have liked two bar graph displays, I had only enough IO to fully support one of them. So rather than just hard-coding the chip to display one value, I added a small button (you’ll notice it just to the left of the MCU, the largest IC) which allows you to select between displaying the instantaneous voltage input (VU meter mode) and the current wiper position. This proved very helpful in debugging.
For simplicity, I’ve chosen to use the ATMega’s internally 8Mhz RC oscillator. Timing isn’t very critical for this application, so I coded up a simple delay routine which works based on repeating an operation for a calibrated number of iterations. I probably could’ve done a better (more generically useful) job on the look-up table too, but with only a few datapoints, I didn’t feel like spending much time on it. Lazy me. Anybody out there have some good code written for interpolating look-up tables? 🙂 Well, what I do have, you may download via the links at the bottom of this post.
In order to demonstrate the performance of my device, I created an MP3 containing about 90 seconds of audio (music) at different volumes. This file was played back on my laptop, whose volume setting was adjusted until the loudest sound produced just barely hit the maximum input of the ADC. The Audacity screens you see below show the original waveforms on the top and the “leveled” waveforms on the bottom (recorded via my laptop’s microphone input jack). Enjoy:
As you can see and, hopefully, hear (sorry about my lousy sound card – the signal is much clearer than it sounds here) in the video above, the soft parts in the original track were amplified somewhat, while the loud parts were reduced in volume. If not aurally obvious, I suspect the results are pretty easy to see in this screenshot from Audacity:
Overall I think this project turned out alright. I particularly liked the method of first prototyping with LabVIEW, the bus pirate, and my IOBoard. This definitely sped up development of the final application and made debugging easier. The only thing I wish is that I could use my VI to generate code for the AVR. Such a thing is available, but only for 32-bit processors. Oh well, maybe it’s time for me to upgrade by a few bits.
In the future, I may try amplifying the audio outputs. Obviously, potentiometers by themselves can only reduce signals. And that’s fine, as long as my input source is sufficiently loud on even the soft songs (because we can always cut the volume of the loud pieces). But for even greater range, amplification would be nice.
You may wonder why I didn’t include any other adjustments, like an additional volume control, or knobs to adjust the filtering speed (these would be the attack and release knobs, which, by the way, are awesome names). Well frankly, I wanted to keep things simple. The point of this project was to reduce the amount of knob-turning I do. If I left things adjustable, I’d probably be tweaking stuff all the time. It’s just a compulsion.
Anyway, I hope you’ve enjoyed this project as much as I have. As always, if you have questions, comments, or want help building something like this, please leave a comment!
List of Parts Used, with Prices (Excluding Passives)
A couple of weeks ago I spent some time examining a fairly complex circuit board from my old, but still functional, clock radio/CD player. I was using the probe of my handheld multimeter to measure voltages at various IC pins and circuit traces. At one point during the process I thought, “Gee, wouldn’t it be nice if I had someone here to read the voltmeter to me as I test various points? That way I could focus on my probe and not accidentally short neighboring pins.” But then I realized that I did have someone to do just that: Microsoft Sam. I present to you the NI LabVIEW talking voltmeter:
For those of you without LabVIEW, here are a few screenshots of the subVIs shown in the video above. Don’t forget that you can always download a free, unrestricted 30-day trial of LabVIEW from the NI website (seriously, it’s awesome, you should try it).
First, the initialization VI, which opens the Microsoft ISpeechVoice reference:
Next, the blocks responsible for detecting new steady-state voltages:
Finally, the code which converts numbers into strings and sends them to Sam:
Many thanks to Grant Heimbach, whose sample speech VI saved me a lot of development time (his original code is available on the NI Developer Community).
Click here to download a simple example VI which utilizes the code shown above.
The full, unmodified IOBoard Voltmeter program, as well as all of its supporting components, can be located at the bottom of the Mobile Studio Downloads page.
Got questions or comments? As always, feel free to leave them below!
Lately I’ve been doing a lot of work on a fairly complex LabVIEW application. It utilizes a number of controls and indicators which have vertical scrollbars (e.g. text boxes and arrays). This gives users access to a great deal of data without over-crowding the front panel. Unfortunately, LabVIEW does not natively support scrolling via the mouse wheel. Well, I happen to like the scroll wheel, and so do many of my users. So this week I finally went online and found a fairly straight-forward means of implementing this functionality. You don’t need any crazy DLL calls or APIs; LabVIEW’s “Input Device Control” palette offers this functionality (located under the “Connectivity” section). I found this particular example very helpful.
I did run into one rather odd problem though. On my computer, the “Scrolling” value I get using the “Acquire Input Data” block is differential, not absolute. In other words, when I move the wheel one step, the value of “Scrolling” is 120. If I scroll no further and poll again, “Scrolling” is back to 0. However, on the second computer I used to test this function, I received an absolute value. In other words, if I move the wheel one step, the value of “Scrolling” is 120, but then it remains at 120 until I move the wheel again. For instance, if I move the wheel by one additional step, “Scrolling” is now 240; the overall value is cumulative (an absolute position).
To combat this problem, I came up with a fairly simple (but not exactly elegant) algorithm that attempts to detect the mouse type based on the first few values of “Scrolling.” If the absolute value of this parameter ever exceeds 3000, we assume the mouse is of the “absolute” variety. Otherwise, if we’ve seen this value change more than 10 times without exceeding 3000, we assume it is of the “incremental” variety. Until these 10 changes are seen, all scroll input is ignored. This can be a bit frustrating, since the first time a user attempts to scroll something, they’ll have to move the wheel for a second or two before the GUI responds. However, in the case of my application, I then store the mouse type in the registry, so that the next time the program is launched, the scroll wheel is immediately functional. No big deal. If anyone out there has better ideas though, feel free to let me know!
Anyway, you can click on the block diagram below to get a larger view of a quick demo application I built. This demo shows how you might use the scroll wheel to control text boxes, arrays, sliders, and even graph axis scaling. You can also download the example ZIP file below. Feel free to leave comments or questions below. Thanks!
Well it’s hard for me to believe, but I’ve now been using National Instruments LabVIEW for six years. I started off with LabVIEW v7.1 and have used every version since (it’s now called LabVIEW 2010). So I’m definitely a fan. And yet, I’m still discovering new and more useful features on a fairly routine basis.
Just the other day I was looking for a convenient means to save a ton of front panel control values to a file (to allow the user to save their current program setup for later use). In the past, when saving program settings, I’ve used the Configuration File VIs, which make saving simple parameters very easy and neat (think header-based *.ini files). However, in this case I needed to save an obscenely large quantity of very variable data (numerics, strings, booleans, arrays, clusters, clusters of arrays, arrays of clusters, etc…). After a bit of Googling I found my answer, the magical Datalog VIs (located on the Programming → File I/O → Adv File Funcs → Datalog palette):
The Datalog VIs allow you to save clusters of any sort to a compact, record-based file. Of course, you aren’t required to make use of the record functionality, you can just store everything in a single record and then recall that record later. For instance, I packed the following mass of controls into a single datalog file with just a few simple blocks:
Reading from a datalog file is just as simple, you need only swap in a read block in place of the write block. Here’s a bit more on the Datalog blocks, taken from NI’s website:
A datalog file stores data as a sequence of identically structured records, similar to a spreadsheet, where each row represents a record. Each record in a datalog file must have the same data types associated with it. LabVIEW writes each record to the file as a cluster containing the data to store. However, the components of a datalog record can be any data type, which you determine when you create the file.
For example, you can create a datalog whose record data type is a cluster of a string and a number. Then, each record of the datalog is a cluster of a string and a number. However, the first record could be (“abc”,1), while the second record could be (“xyz”,7).
Using datalog files requires little manipulation, which makes writing and reading much faster. It also simplifies data retrieval because you can read the original blocks of data back as a record without having to read all records that precede it in the file. Random access is fast and easy with datalog files because all you need to access the record is the record number. LabVIEW sequentially assigns the record number to each record when it creates the datalog file.
So there you have it! If you’re looking for an easy way to store diverse data, the Datalog VIs may be your answer. Oh, and if you’ve got no clue what graphical programming with LabVIEW is all about, check out NI’s free 30-Day LabVIEW Trial. The trial is full-featured; the only catch is a small watermark placed in the lower right-hand corner of your VIs. But give it a shot! It takes a little getting used to if you’re a die-hard C programmer, but once you understand the basics, you’ll be churning out sophisticated programs in a fraction of the time required to type code.