Friday, February 14, 2020

Understanding Git, Part 1

This is the transcript of the talk about Git version control system that I have presented to my colleagues. The talk consists of 3 parts, in this post I'm publishing the first one.

Welcome to the talk about Git. Git is the tool we use every day. Still, Git can remain a mystery to lots of people, and they resort to “cookbook” approach, using Stack Overflow as a translator between their needs and Git commands. This isn’t surprising because Git is really complex—every command in it is a Swiss army knife which can do lots of things. Because of that, the description of each command’s parameters usually takes several pages. And what’s worse, the help pages assume that the reader is familiar with how Git works and use Git-specific jargon extensively, making things even more confusing.

The purpose of this talk is to demystify Git, the goals of the talk are listed on this slide. We start by explaining from a high level what Git repository is built from and how its parts are connected. This will help understanding the role of each command, how to apply the commands to everyday tasks, and how to get out of trouble.

We start with understanding the building blocks of a Git repository. Quoting Fred Brooks, “Show me your data structures, and I won't usually need your code; it will be obvious.” In this description we stay away from Git commands in order to concentrate on abstract properties of the data they operate on.

At the storage level Git is only interested in the contents of objects and their type. Git repository is a content-addressable storage. Each object is immutable and is permanently referenced by the SHA1 hash calculated from its type and contents. Git uses file system as an index, making it easy to find an object by its hash. Effectively, Git repository is a huge hash table.

Git itself doesn’t impose any restrictions on the size of objects. But usually there are practical limitations from the underlying file system.

Does an object with no content even make sense? Yes, it does. For example, we might need to store an empty file in Git repository. The interesting point is that since the type of the object is added to the content in order to produce the hash, every empty object of each type has a distinct hash.

UPDATE on SHA-1 and collisions: Linus noted a long time ago (in 2006) that having a collision is unlikely in the lifetime of the Universe and shouldn’t be a big problem. However, as clever engineers from Google have demonstrated it is possible to create a SHA-1 collision deliberately. Following this result, GitHub has added measures to detect them (but they admit you need to have huge resources in order to generate such a collision). The Git project is working on a transition from SHA-1 to SHA-256 hash function. The release notes for Git 2.24 says “Preparation for SHA-256 upgrade continues.”

Blob is the simplest type of Git object. It stores the contents of a file. Due to the principles of objects storage explained above, changing even a character in a file produces another blob. Because the path and the name of the file is not stored in the blob, two files with the same content are indistinguishable at the storage level and occupy the same blob. However, this also means there should be a way for finding a file from its name and path.

This is what tree objects are for. Each tree object corresponds to a directory in the file system. The tree object consists of nodes. In the simplest case a tree can be empty and have no nodes at all. If it does have them, each node stores a name, file system mode, and the hash of the object. Since trees are stored as objects, they have associated hashes, too. Thus, a hash in the node entry typically points out either to a tree object or to a blob object.

This is an example of a tree object. The leftmost column is the file system mode which uses traditional UNIX octal constants, then we see the object type which is in fact extracted from the object contents. The contents are accessed by the hash listed on the next column. The rightmost column is the object name in the file system.

On this slide we see how a tree object references a blob and another tree object.

UPDATE: Note that since multiple directories and multiple files from the file system with the same content will “collapse” into the same tree or blob object in Git’s repository, tree nodes actually create a graph structure—there can be multiple paths from the top level tree object to a leaf node.

From these explanations we can conclude that Git always stores full contents of each file and directory. And every stored change to the project is like a snapshot of the working directory. Git doesn’t store deltas between file states as some other version control systems do.

This model may appear wasteful, but as a change doesn’t typically modify all the files, it’s actually not that bad. Also, underneath the storage model Git uses compression to save space. The benefit of this model is that if we take a top-level tree object of a snapshot, we can very quickly restore our working directory to that state.

Each change is stored by Git as a commit object. This is a richer object than previous two. A commit stores a hash of the top-level tree object of the snapshot. It also stores information about the change: who and when prepared it, and who and when committed it into the repository, along with a human-readable description of the change, which is often called a “commit message”.

As a Git object, a commit also has a hash. Each commit stores 0 or more entries to previous commits called “parent commits”. This arranges commits into a directed graph.

Git doesn’t impose any restrictions on the number of parents that a commit may have. Linux kernel repository has a famous commit with 66 parents. A less practical example on GitHub shows a commit with 100,000 parents. Again, the restrictions here typically result from the size of the commit object as a file in the filesystem and the time that is needed to process commit objects.

Speaking of other “edge cases” of commits, they may have no parents, too. For example, the very first commit in the repository doesn’t have a parent.

Yet another “edge case” is when two subsequent commits refer to the same tree. That means that the later commit doesn’t really have any changes compared to the previous one. It is called “an empty commit”.

Note that since each commit always contains one reference to a tree, operations on trees can accept commit hashes and trivially resolve a commit into the corresponding tree.

Let’s examine the commit graph more closely. It’s a very important structure in the repository. As we have seen before, more recent commits refer to older commits as their parents. There are no back references though, so traversing commits in a forward chronological order is not efficient.

The crucial relationship between the commit graph nodes is whether one commit is reachable from another—that is, whether there is a path from one commit to another. Since a commit can have multiple parent nodes it’s possible to have multiple paths. Obviously, the initial commit is reachable from every node of the commit graph.

For some tasks it’s efficient to organize commits into a list. The list can order the commits in arbitrary ways, and even gather together commits that are not reachable from each other in the commit graph.

Lists help in disambiguating graph navigation. For the provided graph example, if we say “go 3 commits back from the commit G”, since the commit "G" has multiple parents, there are multiple paths to follow. But if we provide a list, there are no doubts what we have meant.

A lot of git commands operate on the commit graph. It’s important to understand what operations can be executed on it. As we mentioned earlier, the most important operation on the commit graph is finding paths between two commits. Very often we are looking for a path between some commit and the initial commit— such a path always exists.

The path can be represented as a list of commits. Then the list of commits can also be considered as a set—as there are no cycles in the commit graph, any commit can only occur in the path once. Then we can apply the usual mathematical operations to this set: finding a complement of the set and performing usual operations on two sets: combining them, intersecting, and finding commits that present in one set, but not in another.

There is also once particular operation which is useful for version control—if we have two paths going from commits "A" and "B" to the initial commit, it’s obvious that they will share a subpath. If we want to exclude these shared commits from our consideration, we make a union of the path differences. In mathematics, this operation is called “symmetric difference”.

Finding a path doesn’t change the graph. The only way we can change the graph is by adding another node to it. I’m sure you have used “amend” and “rebase” commands in Git, and it might look as if you are modifying a commit. Understand that in fact you create new commit or commits.

Sure enough, this approach raises a question on efficiency. If commits can only be added, how to prevent an unbounded growth of our repository? Git manages that using garbage collection technique. There exists a set of garbage collection “roots”. They comprise what the repository users do care about. Objects that are not reachable from the roots are considered garbage.

Please note that since the commit references go backward in time, the initial commit doesn’t “hold” any other commit, in fact the opposite is true.

Let’s consider another thing specific to version control systems—patches (or diffs). As I’ve mentioned before, Git doesn’t store patches in the repository, but generates them as needed. Typically the patch is generated by comparing 2 tree objects. What do patches consist from?

The core of the patch is the sequence of modifications done to the files. First, the file itself can be renamed, added, or removed. Second, there can be modifications to the contents of the file. Each modification is a sequence of so called “hunks”.

An optional part of the patch is the change description. In the trivial case, the patch can be empty.

Note that since patches are not stored in the Git repository, they don’t have hashes.

What is a patch hunk, exactly? Git uses so called “unified format” for patches, and this is how a hunk looks like. Before the hunks, there come two lines that contain the old file name and the new file name. Each hunk starts with the line that specifies the line number when the old text starts and the length of the part being modified. Then there is the new line number—it can change because the previous hunk has added or deleted some lines—and the new length.

The change always stores the context—the surrounding lines, as a measure to prevent garbling up the file if it was changed since the patch was generated. The change itself is represented by lines being removed and the lines being added.

As with the commit graph, we can also apply mathematical thinking when considering patches and come up with a “patch algebra”.

For each patch, we can produce its inversion—if we apply the patch and then its inversion, there will be no changes to the file. We can split a single patch into a series of smaller patches. We can also combine them back into the original patch. If we have a sequence of patches, we can change their order, which will in general require changing the patches.

The last building block we are considering is a reference. It’s a very simple object which typically only holds a commit hash. References are not stored in the repository hash table and don’t have an associated hash. Instead, references are stored as files in the file system, and the file name is the reference name—everything is simple here. Sometimes references have associated metadata.

Usual file operations can be applied to a reference, including updating the commit hash that it stores.

Conceptually, references live on a different abstraction layer. They provide entry points into the graph, giving human-readable names to commits. We can thus use reference names instead of trying to memorize commit hashes.

One of the usages for references by Git is for managing development branches. The “master” branch (or reference) is created by default, but there is actually nothing special about it.

Since references are used by humans, they constitute garbage collection roots. It is assumed that objects not reachable from the GC roots can be removed with no impact on the users. This is an example of this principle in action—once we remove a reference, all commits and associated trees and blobs, if they are not reachable from any other GC root become garbage and can be removed from the repository.

Note that in real life the references are not the only GC root, and actually forcing Git to remove unlinked commits is more involved. This makes recovering from human mistakes easier.

Let’s talk about merge commits. Merges typically occur when we need to integrate changes done on different branches. However, such integration doesn’t always require creating a merge commit with multiple parents.

Let’s consider the situation when “master” is the main development branch, and there is also a branch where some bugfix has been developed. Now we want to have that bugfix in our main branch. Formally, we need to merge “master” and “bugfix” branches. However, if “master” had no changes since the bugfix, instead of creating a new commit Git can simply re-bind “master” branch to point to the same commit as the “bugfix” branch. This is called “fast forward” in Git.

If doing a fast forward merge isn’t possible, Git must create a merge commit. In this example, the commit “M” is a merge commit which has commits “A”, “B”, and “C” as parents. What is the problem with this?

First, there are now 4 trees to deal with. If we want to see how does “M” differ from previous commits, we need either to perform 3 pairwise diffs, or to use what is called “combined diff” which looks more complicated than a usual diff.

Second, when navigating through a commit graph, a merge commit creates a fork. There are several paths to take when going from a merge commit to its ancestors.

That’s why some people advise against using merge commits in their repositories. This policy is similar to coding style—makes no difference to Git, it’s only up to the people who use it. The alternative to merge commits is rebasing. In this example, instead of creating the merge commit, we could take all the distinct changes from each of those branches and create new commits with the same changes but lined up one after another.

We are at the end of the first part of the talk. Let’s recall what we have learned.

First, recap the layers that comprise a Git repository. The foundation of all is the file system. It is used by the storage layer to store the contents of objects. File system structure is used to find any object by its hash quickly.

Objects can also reference each other using hashes. This forms a structure of the repository. The main structures here are trees and graphs.

Finally, for the convenience of users there is a layer of references that assign human-understandable label to hashes. This layer also directly uses the file system for storage. The labels are in fact file names. The references layer is also used for garbage collection.

This slide is probably the most important one from the entire talk. If you memorize it, you can understand most of Git help pages. It describes the relationships between the building blocks of the repository. The diagram on the left depicts the storage layer, the diagram on the right depicts upper abstraction layers. On the bottom there is a reminder of how patches do get produced.

UPDATE: Just wanted to clarify that lists and patches are transient objects and they are not stored in the repository.

Saturday, December 21, 2019

Understanding Microphone Calibration

A measurement microphone is an essential tool for doing any acoustic system adjustments. Although it can not substitute ears and brain in evaluating the quality of an audio setup, it's indispensable for a number of tasks like speaker alignment.

When aligning speakers we perform relative measurements, for example—are there any significant differences in the frequency response curves between left and right speakers? What about time alignment? For these tasks any measurement microphone working in audio range is suitable, with no calibration required. However, once we start assessing absolute parameters like the shape of the frequency response curve and try "straightening" it, we need to be sure that the microphone itself provides us with accurate data. After all, it's impossible to draw a straight line using a curved ruler!

Here is when microphone calibration comes into play. Ideally, the calibration file describes how exactly does this particular microphone deviates from flat frequency response. Then the measurement program uses this information to compensate the frequency response for the acquired measurement data. Thus, if we measure a reference speaker tuned to flat frequency response (like NTi Talkbox), we should obtain a flat FR line, shouldn't we?

A Bit of Theory

Before answering that, let's consider several basic definitions. If a microphone receives only direct sound of an acoustic source, this is called free field conditions. Because even "omnidirectional" measurement microphones become more and more directional with rising of the frequency, the orientation of the microphone capsule relative to the sound source becomes important in this case. Thus, the main operating condition is when the microphone is pointed towards the sound source—on-axis incidence.

An opposite of free field is when the microphone receives the sound from all directions (random incidence), this condition is called diffuse field. In practice, we mostly deal with reverberant fields—a mix of free and direct fields. Microphones are calibrated to a flat frequency response either for free field conditions or for diffuse field conditions. Due to imperfect omnidirectionality a pressure microphone can not achieve a flat response simultaneously under both conditions. Below is an illustration from G. Bore and S. Peus "Microphones" brochure:

Obviously, when measuring a sound source with a microphone, we need to understand the conditions it was calibrated for. We also need to make sure that we measure under these conditions! A lot of measurement microphones accessible to audio enthusiasts are calibrated for a flat response under anechoic (free field) conditions. That is, for the previous example the calibration file will contain the data for the black solid line, so the measurement program will compensate for its excessive sensitivity at high frequencies. However, in domestic rooms the field is reverberant—there is direct sound from the speaker mixed with reflections coming from all the surfaces surrounding it.

With the advent of computer-based measurement tools simulating anechoic conditions becomes easy. After recording a log sweep, the measurement program performs forward and inverse Fourier transforms on it, obtaining an impulse response (IR). On the IR graph, we can clearly see the initial impulse and the sound contributed by reflections:

If we window the impulse response to cut the reflections, we simulate free field conditions. The drawback is that we also cut out information about the frequencies having wavelengths longer than the window. The approach than can deal with this issue better is called frequency-dependent windowing (FDW). It windows each frequency individually and thus doesn't lose low frequency information. In REW the user can specify how many cycles of each frequency to keep:

Acourate provides more advanced controls allowing to specify cycle count for low and high frequencies, and before and after the main IR peak, all independently from each other:

Now back to the original question—if we measure a speaker tuned to give a flat frequency response under anechoic conditions on axis using a microphone with a calibration file for free field, and then use a fixed gate (window) or FDW on the measured IR, we indeed should obtain a flat frequency response graph.

Practice

Over time, I have acquired 4 measurement microphones of different makes and types:

  1. miniDSP UMIK-1 USB microphone with manufacturer's calibration files: on axis (0 degrees) and 90 degrees;
  2. Dayton EMM-6 analog microphone with manufacturer's on axis calibration file;
  3. Josephson C550H analog microphone with no calibration;
  4. Another miniDSP UMIK-1 USB microphone which I bought from Cross-Spectrum Labs (CSL); it has both manufacturer's (miniDSP) calibration files and another set of calibration files provided by CSL: for 0, 45, and 90 degrees orientation.

There is an interesting story regarding the Josephson microphone. I've reached out to Josephson to ask about calibration files and got the following reply:

We do not include individual calibration data at the price of the C550H, sorry. It is quite time-consuming to do that properly and we would rather not provide unsupported data. We are aware that some companies provide “calibration data” but without any supporting traceability or standard procedure it’s approximately meaningless.

What they mean here is that there exist standardized calibration procedures that are performed by certified labs, and measurements carried out after this calibration can be used in official reports. And C550H is not at the price point to justify these procedures, despite that it's the most expensive microphone of all four I have. Obviously, after reading this statement I started questioning the quality of the calibration files provided by Dayton and miniDSP, especially after I have compared the calibration files from CSL and miniDSP for the same UMIK-1 (Mic 4):

The differences for on-axis response are quite significant—the resonant peak is further up in frequency and is higher by 1 dB! That's interesting, right?

Then I decided to compare all 4 microphones by measuring the same speaker using ground plane technique. The speaker was RBH Sound E3c center channel which I corrected sligtly using Acourate to give it a tighter IR. Since I was doing this experiment in a small room, instead of actual floor I used my daybed:

This speaker due its small size obviously lacks low frequency output, but we can compare everything above 40 Hz. This is how the measurements look when no calibration files are used. The curves are gated using FDW window of 3 cycles to create quasi-anechoic measurement:

All microphones are pretty much aligned except for the Mic 1 (blue) (miniDSP UMIK-1 with manufacturer cal only). If I apply manufacturer's (miniDSP) calibration files for both UMIK-1 mics, they agree very closely:

That is good news, meaning that at least miniDSP are consistent with their calibration method. Note that Mic 1 was bought about 5 years ago, and Mic 4 just recently. The only slightly deviating mic is Josephson (Mic 3, cyan). If we would want to align its measurements with UMIKs, we need to bump its response around 9.7 kHz for 1 dB with Q 1.6.

Also note that Dayton (Mic 2, magenta) still doesn't have its calibration applied. What if we apply it?

Yikes! This is much worse than without calibration. Also note the ruggedness of the calibration data—it works quite bad with smoothed graphs obtained by applying FDW. Now I can see what Mr. Josephson was meaning by "meaningless calibration".

What about CSL calibration data for UMIK-1 (Mic 4)? Here is the graph of Mic 4 with CSL calibration applied (green), Dayton with its calibration (magenta), and Josephson (cyan). I've smoothed UMIK-1 by 1/12 octave and Dayton by 1/6 octave:

We can now see that UMIK-1 with CSL calibration is much closer to Josephson, and Dayton with its questionable "calibration" is nevertheless expressing some similarity, too. Since we know that CSL calibration data is for achieving flat response under free-field conditions, we now know what the target for Dayton and Josephson was, although Josephson this time will need to be bumped down at high frequencies by about 1 dB to match CSL calibration.

Preliminary conclusions we can make so far:

  • Cross-Spectrum Labs and Dayton calibrations are for achieving flat response at free-field (anechoic conditions). Dayton's calibration data doesn't seem to be of a high quality, though.
  • miniDSP calibration for UMIK-1 looks more suitable for a flat response under reverberant (or diffuse?) field conditions. Dayton w/o calibration file also shows similar behavior.
  • Josephson sits somewhere in between. Its high frequency response needs to be decreased by about 1 dB to achieve flat response under anechoic conditions and bumped up by 1 dB to achieve flat response under reverberant conditions.
  • It's better not to use miniDSP UMIK-1 without its calibration file as in this case it's behavior doesn't match well any conditions and different mics show significantly different behavior.

Test

What if we actually took a sound source with a frequency response that is flat in anechoic conditions and try measuring it with these microphones? I recalled that a couple years ago I was using NTi Talkbox as a reference speaker and actually still have those measurements. At that time I only had Mic 1 (UMIK-1 with miniDSP calibration). However, having the results from the previous experiment we can derive transfer functions for transforming measurements done with that microphone into other ones. Although that will not be as precise as actually measuring, but still we will get a good approximation.

This is how NTi Talkbox frequency response looks under anechoic conditions (from its specs):

It should be reasonably flat from 100 Hz to 10 kHz on axis. And this is how it was actually seen by Mic 1 on axis from the same distance (0.5 m) with a FDW window of 3 cycles applied:

Irregularities below 300 Hz are due to room modes—they need to be ignored. But look at the bump at 7.9 kHz—that's clearly due to insufficient compensation of the microphone coincidence bump in free field conditions! This confirms that miniDSP factory on axis calibration is not for a flat response under anechoic conditions.

In order to predict how would Mic 4 have done the same measurement I used the following formula:

NTi4 = NTi1 * (M4 / M1)

Which means, we derive a transfer function that transforms the measurement done by Mic 1 into a measurement done by Mic 4 and apply that function to the measurement of NTi Talkbox performed using Mic 1. Then applied the CSL calibration for Mic 4 and got the following:

Now, this is almost flat! Though, we can see a 0.5 dB roll-off at high frequencies, it's not clear whether it comes from windowing, or due to imperfection of our simulation method. Unfortunately, I can't make a direct experiment because I don't have access to that Talkbox anymore.

Josephson also shows a good result in this simulation:

Confirmed—a speaker tuned to flat for free-field conditions indeed measures as flat under quasi-anechoic conditions with a free-field calibration applied. Also confirmed that factory calibration of UMIK-1 mics is not for a flat response in free-field.

Conclusions

Choose the right tool for the job. In order to tune a speaker to a flat response in free field I would choose either UMIK-1 with Cross-Spectrum Labs calibration or Josephson C550H. For measurements in a reverberant field UMIK-1 with factory calibration and Dayton with no calibration can do a good job. In fact, the tuning of Josephson hits a sweet spot allowing it to be used for both kinds of measurements.

Note that I only considered on-axis response of those microphones. For a random incidence (90 degrees) the results may be different. Also note that the results for my Dayton EMM-6 may not apply to other Daytons—I don't know how much variability do exist between their mics. On the other hand, Josephsons are known to be pretty consistent.

A question remains how these differences in the target response of measurement microphones do not prevent people from thinking that having a "calibration" for their mic is all that they need, without wondering what was it calibrated for? My answer to this is that people usually experiment with their target curves anyway and make the final decision judging by whether they like what they hear.

Sunday, October 27, 2019

Measuring QSC SPA4-100 Amplifier and Understanding Driving Modes of Speakers

As I had mentioned a couple of times (see this and this posts), I drive my DIY LXmini speakers from QSC SPA4-100 power amplifier. I had chosen it because of its compact form-factor (1U half rack) and power capabilities (4 x 100W channels) that fit perfectly the LXmini use case. Finally, I've got time to do some measurements on it. While I'm very much satisfied with the sound I'm getting from this amp + speakers, there are a couple of questions I want to get an answer for:

  1. What is the difference in output between the cases when unbalanced or balanced inputs are used with this amplifier.
  2. Does the bridged output mode of the amp provide any improvements in THD compared to single ended mode (the effect that I've seen with Monoprice Unity amplifier).
  3. How a more expensive Class D amplifier (QSC) stands in measurements against a less expensive one (Monoprice).

I decided to measure the amp in 4 Ohm output mode driving 4 Ohm and 8 Ohm loads. This corresponds to the nominal impedances of LXmini's full range driver (SEAS Prestige FU10RB H1600-04) and woofer (SEAS Prestige L16RN-SL H1480). For the loads I used wire-wound resistors attached to massive heat sinks.

Single Ended Mode

Output Power

Below is the table of results obtained by driving one channel of the amp with a 1 kHz sine signal from QuantAsylum QA401. The voltage was measured over the load using Agilent U1252B TrueRMS multimeter:

Load, Ohm Input, dBV Output, Vrms Power, W
8 0, unbal 16.74 35
8 -4, bal 20.88 54.5
4 0, unbal 16.55 68.5
4 -4, bal 20.58 105.6

Trying to go above -4 dBV for a balanced input was tripping the input limiter. This is consistent with the manufacturer's specification for the input sensitivity which is +4 dBu = 1.78 dBV ~ -4 dBV of balanced input (doubling of logarithmic voltage is approx. +6 dBV increase). The gain of the amplifier for unbalanced input is 24.5 dB. In balanced mode it's slightly above 30 dB.

Output power figures are also consistent with the manufacturer's specification. Maximum output power is achieved when maximum allowed input is provided. It can also be seen that the maximum is not achievable when using unbalanced output as the input voltage is limited. This is important as miniDSP 2x4 HD only has unbalanced outputs. They are specified as having 2 Vrms = +8 dBu maximum level, so it's possible to hit the limiter when setting the output gain on miniDSP too high.

I must say that the resulting sound power from LXminis together with the subwoofer so far was enough for playing quite loud in my living room. But it's good to know that power output can be increased if I switch to balanced inputs on the amplifier.

Distortion and Frequency Response

For these measurements I hooked up QuantAsylum QA401 in parallel to resistive load. There is a caution in the amplifier manual warning against connecting any output to the ground. I suppose, trying to do that will trip the short circuit detection circuit in the amplifier. So I used differential connection instead, leaving probes ground connectors floating.

The lowest THD was achieved while driving an 8 Ohm load in 4 Ohm output mode (the picture was taken while using balanced input to the amplifier):

Note that there are two small "spikes" around the test frequency which look surprisingly similar to jitter peaks from DAC tests. I suppose, it's totally possible with Class D amplifiers as they effectively sample the input signal. Thus, small variations in the frequency of the triangle wave generator used for sampling can cause some samples to be off by a small amount. Although, there isn't much worry about that as these spikes are below -110 dB from the main signal, so they are inaudible. Harmonics and aliases also look very small compared to the main signal.

Testing IMD shows more severe distortion and strong aliases at about 60 kHz:

Looks like the antialiasing filter is "slow". Indeed, we can see that from the FR graph:

I also saw similar weak filtering on Monoprice's Class D amplifier and at that point decided that it's because it's a rather cheap model. But now I'm seeing the same on a more expensive amp. Looks like manufacturers decided to use a weak filter to avoid compromising power output. Out of curiosity I also tried measuring the frequency response with a real speaker load, hoping that the inductivity of the speaker would act as a low pass filter, but instead I've got absolutely the same graph. It's good to be aware of this issue.

Driving a 4 Ohm load in 4 Ohm mode yields slightly higher distortion figures. If for an 8 Ohm load we have THD+N 0.0074%, for a 4 Ohm load it becomes 0.0115%.

Balanced Mode

This is where things get pretty interesting. I looked up in the manual how to enable balanced mode, and found that this amplifier doesn't have a switch for that. Instead, the manual says "drive both inputs at the same level, connect the positive terminal of Output 1 and the negative terminal of Output 2 to the load":

This forced me to pause and think a bit about what does that mean for Channels 2 and 4 in non-bridged mode. Since there is no switch for the bridged mode, the amplifier always works the same way regardless of whether we use it for driving two channels in single ended mode, or one channel in bridged mode. For Channels 1 & 3 this doesn't cause any issue—the positive wire of the output gets driven by the amplifier. But what about Channels 2 & 4? It seems that they must be driven via the negative wire of the output and in an inverted phase. Is that true? To answer that, first I connected QA401 left and right inputs to both ends of the load connected to Channel 1, L (blue) to "+", R (red) to "-":

We see a natural voltage drop across the resistive load confirming that the amplifier only drives the "+" wire. What about Channel 2 (connections are done the same was as for Channel 1):

Yes, it's completely opposite—the "-" wire is active! For checking signal phase I connected the left input of QA401 to "+" of Channel 1, and the right input to "+" of Channel 2. Since the positive wire of Channel 2 receives attenuated signal, I adjusted the attenuator on Channel 1 to make the levels to be similar:

In time domain, we can see that Channel 1 and Channel 2 are driven in opposite phases.

Wait, does it mean that Channels 2 and 4 have inverted polarity when the amplifier is used in single ended mode? Actually no, because speakers are differential devices. I'll talk about this later. Just in order to verify that the polarity is correct, I connected two identical speakers the same way to Channel 1 and Channel 2, placed a microphone between them and ran Acourate's "Microphone Alignment" procedure:

As we can see, both speakers are in phase, no need to worry. Let's continue to measurements.

Output Power

I ran a couple of measurements into 4 Ohm load in bridged mode from an unbalanced input.

Load, Ohm Input, dBV Output, Vrms Power, W
4 0, unbal 32.77 268.5
4 -4, unbal 20.63 106.4
4 -10, unbal 10.33 28.4

As we can see, the voltage gain in bridged mode from unbalanced input is the same as for single ended mode from balanced input—30 dB. Doubling the output voltage allows for almost 4x increase in the output power—compare 68.5 W into 4 Ohm from 0 dBV that we have seen for the single ended mode vs. 268.5 W from the same input in bridged mode. Nice! But what about distortion?

Distortion

Unfortunately, distortion doesn't look good. I had to lower the input level to -10 dBV to avoid clipping on the input of QA401, and distortions graph from a 1 kHz input looks like this:

Two tone distortion produces high levels of ultrasonic noise (from same -10 dBV level):

And remember, that's for 10 Vrms output (28.4 W power). In single ended mode even 20 Vrms output produced much less distortion. Clearly, the bridged mode of this amplifier is designed for something like PA applications, not for high fidelity.

Conclusions on the QSC SPA4-100 Amplifier

Answering the questions I've stated in the beginning of this post. We can see that this QSC amplifier is way more linear in its best operating mode (single ended) than cheaper Monoprice in its best mode (bridged)—just take another look at the graphs in the post about Monoprice.

Also, QSC's capabilities are specified much closer to real measurements than what Monoprice had specified. And clearly, higher price point of QSC is fully justified.

The bridged mode produces higher distortion even at lower input signal levels. This can be explained by the fact that each driving amplifier in this case "sees" twice less load. As we have observed on the 4 Ohm vs 8 Ohm load, distortion in this amplifier increases as the load impedance decreases. I suppose, it increases even more with 4 Ohm load gets divided in half by bridging.

What is common for both amplifiers is that there are some visible ultrasonic artefacts that are not filtered out even when using a real inductive speaker load. So actually, driving some sensitive speaker at high output level may overload and even damage it due to excessive high frequency energy.

Speaker Driving Modes

We can see that the audio engineers at QSC are very creative. As we have observed, the same speaker can be driven by this amplifier in 3 modes:

  • from the "+" terminal in positive phase;
  • from the "-" terminal in inverted phase;
  • from both terminals.

Does it make any difference to the speaker? In fact, no because what speaker "sees" is the difference of potentials between its "+" and "-" terminals. Say, we have 1 V (relative to some arbitrary reference point) applied to "+" terminal, and 0 V applied to "-" terminal. The speaker "sees" 1 V - 0 V = +1 V voltage. This voltage drives the cone forward (if enough current is supplied by the amplifier).

What if we apply 0 V to the "+" terminal and -1 V to the "-" terminal? The speaker "sees" 0 V - (-1 V) = +1 V voltage. This voltage drives the cone forward. Now, what if we apply 0.5 V to the "+" terminal and -0.5 V to the "-" terminal? Absolutely the same thing.

This is why it's possible to drive a speaker from the "-" terminal using an inverted signal. The speaker will behave the same as if driven from the "+" terminal using the signal in the original phase. Same thing happens if we drive the speaker from both sides. The only participant for which bridging matters is the amplifier. After internalizing all this stuff, I've re-read the post from Benchmark Media about myths of balanced headphone connections and this time I understood every word from it. Practicing with amplifiers helps to understand the theory!