Sunday, February 23, 2020

Understanding Git, Part 2

Publishing the next part of my talk about Git. See the first part here.


Let’s continue to the second part of the talk. Whereas the building blocks we were considering in the first part are more or less abstract and can be used for understanding generic principles of any distributed version control system, the areas and especially commands are specific to Git.


Let’s start with Working tree and Index. The Working tree is something that is understood by all Git users—it’s their sandbox where they do all your edits and file structure manipulations. It can be also referred to as “Working Directory”.

As for the Index, it’s a more confusing area and depending on your experience with other version control systems, index can be either a benefit or a hazard (rephrasing Rick Dekkard). Some version control systems do not have index, thus changes in the working tree are immediately committed into the repository. Not so with Git. Here, Index is an intermediate area where the next commit is staged. That’s why Index is also referred to as “Staging Area”. In order to commit a change to the file, it must be first registered with Index.

Because Index sits between the working tree and the repository, it is also referred to as “Cache”. No wonder why reading Git’s messages and help pages related to Index can be very confusing!
Speaking of the building blocks we were considering earlier, both Working tree and Index can be represented as Tree objects. Also note that both Working tree and Index are optional and are only needed to prepare new commits. On a headless Git server they do not exist as there are no users who would commit changes to the server repository directly.


As an example, let’s consider typical output from the git status command. The above output appears when there are changes both to the index and to the working tree. The changes that have already been registered with Index are listed first. Changes that haven’t yet been registered are listed at the bottom.

Note that the same file can be listed in both sections meaning that some of the changes in it are in the Index and some are not. That illustrates the “benefit or a hazard” behavior. On one hand, index allows you to split the changes your are making to the working tree into several commits. But this also can produce an unfinished commit that misses some changes you’ve done in the working tree but forgot to stage.

In this output Git hints us about some commands that can be used in order to move files between the index and the working directory. Let’s explore them.


git add is used to stage to the index any changes in the file. It’s also used to put a file under version control. If you want to undo the changes done to the file in the working tree and use previously staged version, use git restore command. This is a relatively new command and long time git users know that a more universal git checkout command can also be used for the same purpose.

If we want to remove the file from under version control, we use git rm command which erases it in the working tree and also removes from the index. If there are any untracked garbage files or directories in the working tree, they can be easily removed using git clean command.

If we want to see the changes done to the files in the working tree compared to the staging area, we need to use git diff command which generates a diff object.


Moving onto the next level, let’s consider how do we move changes between the index and the repository. The git commit command is well known—it creates a commit object that points to the tree object of the index and adds both to the repository.

In order to see the changes before we commit them we can use git diff with --cached argument—it shows the difference between the index tree and the tree from the last commit.

Previously mentioned git rm and git restore commands can be used to remove or undo changes in the index. Note that there is an inconsistency between the argument names they use.

And of course, there is no direct way to remove a committed change from the repository.

An interesting question: what does it contain in a “clean” state, e.g. after doing git checkout or git reset? A good answer to this question is in the “Reset demystified” section of “Pro Git” book. On checkout, the tree of the index is the same as the tree from the checked out commit (and same as your working dir assuming you haven’t made any changes to it yet). Thus, both git diff and git diff --cached produce an empty diff immediately after a checkout. If we make a change in the working directory to an already tracked file, git diff shows it. Once we stage this change (git add), it is now shown by git diff --cached, but isn't shown by git diff.


Finally, for those users who don’t want to fiddle with the cache, there is a way to work through it, as if it didn’t exist. Using -a argument with git commit command automatically puts all the current changes from the working tree into the index before creating a commit.

git reset with --hard argument can be used to replace the contents of the working tree and the index from the last commit in the repository directly. And git diff HEAD (all caps) can be used to show combined differences between the working tree plus index and the last commit. git diff HEAD puts the tree of the working dir on top of the tree of Index and diffs the result against the tree of the last commit (so if there are both staged and unstaged changes to the same line the latter will be shown).

What is HEAD, exactly? It’s a special reference in the repository that we consider next.


This is the already familiar diagram of the commit graph with branches. HEAD usually points to what is called “current” branch—the line of changes we are working on. Note that HEAD is a meta-reference—instead of pointing to a commit via its hash it instead contains the branch name, thus when we use HEAD, double dereferencing is needed to get to the commit.


This is how HEAD can be used and changed. When we do a commit, git commit uses HEAD to get to the last commit on the current branch and make it the parent of the freshly created commit. Then git commit updates that branch to point to the new commit.

git branch command can be used for creating a new branch without switching to it. A relatively new command for changing the current branch, and thus updating HEAD is git switch. Note that git switch will refuse to work if there are any uncommitted changes in the working tree, because switching to a branch also means populating the working tree contents using the last commit of the branch, and any uncommitted changes will be lost. In case we actually want to lose them, we can use git reset --hard and provide it with the branch name.

git switch with -c argument is used for creating a new branch and switching to it (that is, updating HEAD).

People often ask, what are the differences between relatively new git restore and git switch commands vs. good old git checkout and git reset. There is an excellent write-up on GitHub blog about this. In short, git restore and git switch are safer alternatives to old commands, but they don’t replace them fully. There are some similarities in their arguments, e.g. for interactive piece-by-piece restoration both git reset and git restore use -p argument, but there are differences too, e.g. git switch -c is the same as git checkout -b. In general, the arguments of the new commands should be more straightforward and easier to understand.


The old multi-purpose git checkout command can be used for changing HEAD, too. Unlike git switch which only accept branch names, git checkout can switch HEAD to any commit.
However, if the commit is not referenced by any branch, switching to it creates a so called “detached” HEAD. This isn’t a normal state for development as git commit will not be able to refer the new commit from any branch. That means, the new commit will be considered “garbage” by Git and removed from the repository sooner or later.

That’s why Git displays this lengthy message when you switch to a detached HEAD. BTW, it also hints you about a quick way to flip between the last two branches by passing minus (-) argument to git switch. The takeaway is—if you see this message, don’t commit any changes unless you really know what you are doing.


As Git was created for distributed development, it natively supports synchronization with remote repositories. “Remote” doesn’t necessarily means that data is on another machine, it can reside in a different folder on the same hard drive.

To add a remote, use git remote add command. As you can guess, Git sets no artificial restrictions on how many remote repositories you may have.

Let’s do a brief recap on the main commands dealing with remote repositories. git clone allows starting a new local repository from a remote one. It’s a shorthand for creating an empty repository, adding a remote and syncing from it. There are actually two commands for syncing: git fetch and git pull, we will consider the differences between them later. git push is used to sync your local changes back to the remote.


You never manipulate the contents of remote repositories directly. When you sync from a remote repository, all its objects and references get imported into your local repository, thus it effectively becomes a cache for remote data. After you’ve made any changes which means you have created new objects and changed references, these changes can be synced back to the remote using the same synchronization process.

As Git was designed as a peer-to-peer system, it doesn’t matter which side initiates data transfer. The remote side can request data from you, or you can push your local data to remote side. In the latter case you must have the necessary permissions.

Now let’s talk about the difference between git fetch and git pull. The former doesn’t change the state of your branches or the working tree, it just does the synchronization. git pull is a shorthand for doing git fetch and then executing git merge. As we have discussed before, merge commits might be discouraged for your project, in this case don’t use git pull unless you are sure that you don’t have any local changes.


Here is an example of using git remote command. First, we list the remotes by passing the -v argument. There are two remotes here, one called “aosp” which is accessed via HTTPS, and another called “internal” which lives on the same machine. It’s possible to use different URLs for fetching and pushing.

Then we execute git fetch command to receive any new objects and references from the remote called “internal”. After it has finished you can access the contents of objects received from the remote.


As we know, branches are the entry points to repository objects. Obviously, it’s a good idea to use branches of the remote repository to access its commits. From the building blocks point of view they don’t represent anything new—they are also files pointing to commits. However, because conceptually they are in remote repository, they can’t be updated in any other way than syncing from a remote. For the same reason, if you attempt to do a git checkout using a remote branch name, this will result in a detached HEAD.

The name of a remote branch reference is the same as the name of the branch in the corresponding remote repository. For namespacing purposes, names of remote branch references are prefixed with the name of the remote.


And here is another term from Git vocabulary—a ”tracking” branch. Since remote branch references are read-only, in order to do any development on a branch coming from a remote repository—it is called “upstream” branch—you need to have a local branch. Usually it has the same name as the upstream branch, but that’s not mandatory. The name of the upstream branch is stored in the metadata of the “tracking” branch.

If you attempt to switch to a non-existing branch using the same name as one of remote branches has, Git will automatically assume that you want to create a new branch that should track that remote branch. In order to have control over this behavior, you can use --track argument of the git switch command.


Here is an example of what happens when you switch to a local tracking branch after doing git fetch. If there were remote changes on the upstream branch since the last sync—the last common commit is labelled “C” on the diagram—Git will offer to merge your local branch with the remote branch to integrate the changes together. Since these branches have diverged, a fast forward is not possible, and a merge commit will need to be created.

If you are not allowed to have merge commits, then instead of doing a merge you should rebase your changes on top of remote changes. We will consider the mechanics of this a bit later.


Enough with remotes, let’s talk about the tool that helps handling work interruptions. Sometimes you are in the middle of working on a change and then something unexpected happens: your previous change has caused a build break and a simple fix is needed immediately, or you’ve got some ideas and need to test them quickly on some other branch. You don’t want to lose your current changes, but you also don’t want to commit them as is.

One possible way of handling this is to create a branch and commit your current changes there, then checkout another branch to restore the working tree contents to a known state. However, there is another more convenient solution—to stash the change away using git stash command. This command saves your current index and working tree modifications into the repository as commits and brings the working tree and index into the previous committed state.

In terms of building blocks, Stash is just another reference, like a branch. However, the last stashed commit does not use the hash of the previously stashed commit as a parent. It’s actually interesting to understand how stashing is implemented.


Let’s say we have the last commit called “M”. We have made some changes and added them to the staging area, then we have also made some changes to the working tree and haven’t yet staged them. Now we execute git stash, what happens?

First, Git creates a commit from the contents of the index, setting “M” as a parent—as if we have executed git commit command. This commit is shown as “I” on the diagram. Git also snapshots working tree contents and creates another commit for them, called “W” on the diagram. This commit has two parents: “M” and “I”, so this is a merge commit. The stash reference is then set to point to that commit. After that, Git resets the index and the working tree to the state of the commit “M”.

This brings up a question: if we do git stash multiple times, how can we find previously stashed commits?


To answer the question, we introduce another powerful tool of Git—the Reflog. Speaking in terms of building blocks, this is just a list of commit hashes. The power comes from the fact that Git adds a node to this list every time any local reference changes. This includes HEAD, branch references, and Stash.

So, answering the question we have previously stated—in order to find previously stashed commits we can look into the reflog of Stash. git stash list command does exactly that.
Another useful application for reflog is finding commits that you have accidentally unlinked from branches. You can find their hashes either in the scroll buffer of your terminal—if they are still there— or in the reflog.

Here is an example of executing git reflog command. It lists the history of changes to HEAD. The reference specifications in the reflog commits list use special syntax which we will consider later.

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.


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.

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.