Sunday, March 15, 2020

Why the delay?

Yes, it's been a while.

No, I'm not suffering from lack of inspiration. Quite the contrary!

What I'm primarily lacking is time.

Beyond that I'm short on the sort of focus that would enable me to make good choices as to which concepts/features to include initially, which to leave for later while making design decisions that smooth the path for their inclusion, and which notions to abandon as unworkable.

I've lived so many digressions with this project, that I'm finding it difficult to even think about without my mind going off in a dozen directions at once.

To a lesser degree, this is also true of implementation choices. I would like to be able to use Swift for everything, but both my own intuition and the advice of others point to using C for the most performance-critical code, at least for the present. This is acceptable; I've written sound-generation code in C before.

In RatioKey, each new note interrupted the previous note. That was a choice I made to get the app out the door, but that choice is no longer good enough. In large part for this reason, the sound-gen code won't be a matter of cut-and-paste.

At the conceptual level, the key idea I keep coming back to, and which I expect to be the primary focus going forward, is that of a 'harmonic structure' — my term for a set of harmonic series which are related to each other by having (a chain of) members in common.

Almost as important, but perhaps too complex for a first pass, is the idea of defining voices in terms of 'secondary harmonics' — harmonics of the members of a harmonic structure — and the variation in their relative and collective intensity over time, similar to an ADSR envelope but on a per-harmonic basis.

Voice definition is a case where even if I were to decide to leave it out of a first effort I would definitely want the design to make allowance for its later inclusion. It opens too many creative doors to let it go.

As an aunt used to say about vacuuming, I'm sneaking up on getting this thing done.

Thursday, September 05, 2019

Pointer to relevant post on parallel blog

I have four blogs, three of which have significant overlap. The post linked below is an example of this.

It's primarily about my (modest) progress in catching up with the latest developments in the Swift programming language, but it bears on the project described here, to which I intend to first apply those developments.

Dog-paddling behind the bow wave

Tuesday, June 25, 2019

More complications, leading to a potential solution

This evening it occurred to me that varying the pitch of a note, while generating its phase from a multiple of the phase of a base tone, might result in artifacts. I'm not certain of this, and cannot yet articulate why I think it could happen, but it seems at least plausible.

A solution also occurred to me, which is to only use the base tone to generate the initial phase of the note, and from that point on track its phase independently. That thought lead to another complication; when you want the varying pitch to come to rest on a specific tone, the phase of the note may not align with a newly generated note of the same frequency.

I first thought about pacing the change in pitch so it would end up phase-aligned on the target frequency. This would work for scripted compositions, but in live performance it isn't possible to know what the target frequency will be until it happens.

So it seems as though a better solution would be to cross-fade from the sliding note to a newly generated note which is stable on the target frequency.

But, if this mechanism (independently tracking the phase of each note, after initiating it using the phase of the base frequency) is in place for notes with varying pitch, why not just use it for all notes, and not have to worry about whether they will remain at a constant pitch?

Applying this technique to all notes would mean that the base tone is only used to initiate new notes, which would mean precision is no longer an issue, so we can dispense with 80-bit floats!

[7/5/19: The thought that set this all in motion, that varying the pitch of a note while generating its phase from a multiple of the phase of a base tone might result in artifacts (noise), remains a matter of conjecture. I haven't yet hit upon a way of determining whether this is an actual concern. However, eliminating the need for 80-bit floats is sufficient motivation to proceed as though it were established fact.]

Wednesday, June 19, 2019

Ground-shifting changes

We've all had a couple weeks to assimilate all that was announced at WWDC, and those who surf the bleeding edge have been very busy getting up to speed and producing blog posts, newsletters, podcasts, and videos paving the way for the rest of us.

Just listing all of the resources already available would be a formidable task, so instead I'll just mention a couple of good starting points.

For anything related to the Swift programming language, the Swift.org website is the center of the universe. What you won't find there is much in the way of links to blogs, newsletters, podcasts, or YouTube channels relating even to Swift development.

That gaps is nicely filled by Dave Verwer's iOS Dev Directory, which does not include a link to this blog, nor should it!

I don't expect to have much to say here for a few months. In the meantime, you can catch me on Twitter at https://twitter.com/harmonicLattice.

Sunday, June 16, 2019

Navigating a larger problem space

On the same day as my most recent post here, I also began a thread on Twitter, in which I laid out the opportunities and constraints presented by various approaches to generating tones by multiplying the phase of a base tone by frequency ratios.

This took several hours, and I had to finish it the next morning, nevertheless, except for a minor glitch or two, I think I managed to get it straight, possibly for the first time.

Only generating tones that are all integer multiples of the base tone is significantly simpler, but taking that simple approach precludes the use of any musical practice involving pitch variation — bending, sliding, or vibrato.

For the purpose of producing a fuller sound, more like a physical instrument, the set of pure tones that are all integer multiples of the base tone is just too confining. Unfortunately, the alternative seems to be to use phases that continue to increase indefinitely, tracking them using high precision floating-point numbers to keep it working long enough to be usable. I keep thinking there must be a clever hack that would make this all unnecessary, but so far this has just lead me down rabbit holes.

The rabbit holes have become a problem because I cannot hold everything in that Twitter thread in my mind at once; I have to deal with it as I posted it there, in Tweet-sized bites, and have more than once lost track of one detail or another.

If you think of a cycle as being a circle, and phase as being an angle superimposed on that circle, or a position on its circumference, continuously increasing phases can be thought of as wrapping, winding, or coiling around that circle.

The need for high precision comes in because this approach involves multiplying the phase of the base tone by a frequency ratio that might have a value as high as 20,000, then discarding everything to the left of the decimal point, leaving only whatever significant figures were to the right of the decimal point. As that base tone phase increases, so too does the result of multiplying it by the frequency ratio, meaning there are fewer and fewer significant figures remaining on the right, and sooner or later insufficient precision to properly use it for the next step, conversion either into an index for a lookup table or directly into the magnitude of a sound wave for a particular sample, by means of an algorithm. Using higher-precision (80-bit) floating-point numbers buys time.

This inelegant approach grates on my sensibilities as a programmer, but, short of returning to only trying to produce tones that are integer multiples of the base tone, I haven't yet found any way around it.

Thursday, June 06, 2019

Cognitive paralysis: hopefully temporary

I'm presently doing a pretty good emulation of a robot that's got itself 'trapped' in a corner its programming is inadequate to escape. With any luck, this will pass, but I consider myself fortunate to have recognized the symptoms and desisted from digging myself even deeper into confusion.

Saturday, May 11, 2019

A Larger Vision: one piece falls into place

Over the past couple months the scope of this project has expanded rather suddenly, from one tightly focused on enabling music based on harmonics (also representable as integer-ratio intervals) to one which is still motivated by the desire to support harmonic tonality, but which also strives to be more generally useful. This means more work, but also something I might actually be proud to release into the world, if and when I get it into a state where it's ready for that.

One result of this reconceptualization is that I'll be repurposing the term "base frequency" from "an intermediary object which may be used in conjunction with the anchor, providing the scalar factor" to something more concrete, the sample rate divided by size of one or more lookup tables used to represent wave forms that aren't easily calculated on the fly, for example sine waves. As such it will be a minor detail of the implementation, not something user-facing, except as the user might be a programmer working with a framework, if that turns out to be the direction the project evolves.

(Update, 06June2019: At this time, ALL custom terminology should be considered temporary and subject to redefinition, replacement, or deprecation. If/when this all stabilizes, I'll post an updated lexicon.)

Wednesday, April 24, 2019

Moving Targets

I've been letting this project steep on the back burner while firming up my understanding of the basics of the Swift programming language, which I will be using, likely in combination with C for the most demanding real-time code. This has been a propitious pause, as it has surfaced rather gaping oversights in how I've thought about what I've set out to do. What follows is the current state of my evolving understanding and intention.

Caveat: My custom terminology is also still in flux, and usage going forward may not correlate exactly with what came before. I will endeavor to nail down this slippery business sooner rather than later.

Most fundamentally, while making harmonic-based melody more accessible is the primary motivation driving my interest in this project, baking that into the design in a form that makes working with or folding in other tonal systems unnecessarily difficult would be a mistake. This is easily accommodated by defining the frequencies of of available tones in terms of floating point numbers rather than integers. To keep compound error to a minimum, these should be double precision (64 bit).

Since, as previously mentioned, the simplest way to calculate sine table indices begins with tracking the per-sample phase of a 1.0 Hz base frequency, there no longer seems to be a clear purpose for the HCF (Highest Common Fundamental). However, I'm not confident this concept won't still prove valuable, so let's put it on the shelf for the time being. If it comes back off that shelf, it might well be under another, hopefully less clumsy name.

If tones can be specified simply in terms of their first-harmonic frequencies in Hz, expressed as double precision floating point numbers, rudimentary support for pitch bending and sliding becomes a simple matter of respecifying that first-harmonic frequency on a per-sample basis. I say 'rudimentary' because I suspect providing such support while avoiding artifacts will turn out to be more complicated than this.

Next there's the matter of the phases of overtones not necessarily being perfectly aligned with (typically trailing) the phase of a tone's first harmonic. For the moment let's call this overtone offset, since accommodating this can be as simple as adding an offset to the per-sample phase calculated for each overtone. That offset might be calculated as a fraction of the first harmonic's cycle time, and applied before conversion to a sine table index, although moving at least part of that calculation outside of the real-time context and passing the result in as a simple quantity would make sense.

Given overtones with phase offsets, the question arises whether we might want the option of defining tones in terms of multiple instances of overtones, each with its own per-sample offset and amplitude. Since this could so complicate real-time calculations that polyphony becomes problematic, I'm inclined to also put this idea on the shelf, until I've given more thought to the possibility of voices with some/all of the complicated rendering having been precomputed.

The main obstacle I see in the path of precomputation is the aspiration to make the sound output responsive to factors like velocity, pressure, up/down-scale movement, and time-to-release, which can't be known in advance. As a workaround, it should at least be possible to capture these while producing a less nuanced rendering in real time, then apply them after the fact, editing as needed to achieve the desired effect.

In any case, multiple overlapping notes using the same tone should be available, each with its own set of overtones and their variable attributes, with offsets also optionally applied to their first harmonics, for the purpose of generating echoes if nothing else. Considering this, providing multiple per-note instances of overtones might simply be needless complication.

Finally, because there's a temptation to withhold functionality from the real-time context in order make sure rendering can happen in a timely manner, this project really wants to split into two components (modes), one (stage) focused on real-time performance, and the other (studio) focused on providing a full set of features. The communication between these two modes is a sort of bidirectional funnel, and needs to be well defined. An advantage of this requirement is that it is an obvious place to look for an organizing principle, around which to build out the rest of the model and basic functionality.

As such, it may also prove a suitable focal point for any open source initiative, allowing 'stage' and 'studio' applications from different vendors to interoperate. But I'm really getting way ahead of myself in even mentioning that. First I need to build out my own project, then maybe I can think about turning it into an open-source project.

Addendum (25April2019): This is not even close to being a final decision, but I'm thinking it makes the most sense to specify, for any given note, the per-sample frequency, amplitude, and phase offset of the first harmonic, and then to specify the same attributes for higher harmonics (overtones) relative to that, although, for the sake of efficiency, it will be desirable to precompute as much of this as can be without sacrificing responsiveness to the performer.

Saturday, February 16, 2019

The Elements of Voice

In a previous post on this blog, I defined voice as "Any attributes in the synthesis of a note other than its basic frequency and the overall volume, for example the ADSR Envelope or emphasis on different harmonics as the note progresses." You can also find a brief explanation of the ADSR Envelope in that same post.

In RatioKey 1.1 (removed from the App Store more than two years ago), I provided the means to edit the duration of each phase of the ADSR envelope, as well as the volume at the point where each phase transitions into the next. This helped make up for that app only being capable of generating a single simple sine wave at a time, with each new note interrupting the previous note, and no support at all for overtones.

Even back in 2010, while working on that app, I wanted to be able to synthesize more interesting voices, composed of harmonics (what I'd now term secondary harmonics), with the intensity of each varying independently over time, and to craft a simple interface for editing such voices, but at that time I had no clear idea how to generate multiple simultaneous notes, much less how to build them from harmonic components.

Over the intervening years, I've ferreted out solutions for various aspects of this problem space, but it wasn't until I'd experienced the absence of phase alignment, motivating a reevaluation of my approach, which led to the idea of 1) determining the Highest Common Fundamental (HCF), 2) tracking its phase on a per-sample basis, and 3) using that phase to generate indices for sine table lookup on a per-sample basis for members of a harmonic structure, that I felt confident I could actually do it. That was, for me, the key missing piece to the puzzle.

In the process of fleshing out that idea, I had another eureka moment when I realized that this approach would not only facilitate the synthesis of any member of a harmonic structure while guaranteeing phase alignment, but it would also enable per-sample modulation of the harmonics of those structure members (secondary harmonics) by the very same method, since they are also part of the harmonic structure.

Given the ability to independently control the intensity of secondary harmonics over time, my sense is that this should supersede the ADSR paradigm. Yes, you might still want to ramp up the volume very quickly, drain some of it back off almost as quickly, then hold it nearly steady for awhile, before tapering off to silence, but this is just as easily achieved by controlling the intensity of component harmonics as by controlling that of the basic pitch.

Per-sample control of harmonic intensities, translated into physical terms, equates to moving acoustic energy around among harmonics, much as we do with our tongues and the way we shape our mouths while speaking. This might be approached with the discipline of an engineer applying the conservation of energy, or utterly fancifully, or anywhere in between. It could be used to mimic familiar sounds, or to create sounds even a veteran sound collector or foley artist would be hard pressed to find in the wild or produce physically.

There are also elements of voice that this approach, as currently conceived, does not support, notably any sort of pitch bending or sliding, except as these might be applied to a harmonic structure as a unit, rather than to individual notes. In the current version, all members of the harmonic structure, including the secondary harmonics, are discrete pitches.

(Yes, it should be possible to support pitch bending and sliding by allowing variable factors relating the HCF to parts of the structure. Strictly speaking, in that event, it would at least intermittently cease to be a harmonic structure. This may be a case where accommodation is more important than conceptual cohesion, and worth the added complexity. Further contemplation is indicated.)

Tuesday, January 29, 2019

From Harmonic Structure to HCF to Sample Value, Part 5: Focusing on Pitch Specification and Alteration

Up to this point I've treated the Anchor (and Base Frequency, possibly not mentioned here since 2010) as more-or-less integral aspects of a Harmonic Structure, but really the Anchor only exists to provide a couple of services.

First, and most obviously, the Anchor is a point of reference for specifying the pitches of the fundamentals of the harmonic series composing the structure, and also of the HCF (Highest Common Fundamental). For this purpose it is enough that the Anchor's own frequency be unambiguous. Tuning would simply involve incremental alterations to that frequency.

The other service the Anchor provides is the ability to move a harmonic structure up/down-scale as a unit, by integer-ratio factors. This is what I previously referred to as "Consonant Transposition" on the theory that such a change is likely to be more consonant than using an irrational factor.

There could be other ways to provide these services, of course, including the option of separating the scalar component of the definition of the Anchor's frequency from the integer-ratio component, by bringing back the concept of a Base Frequency.

The Base Frequency would be specified simply using a Double (double precision floating point value), which you could think of as a multiplication factor that is always applied to 1.0 Hertz.

The Anchor would then be specified as an integer-ratio multiple of the Base Frequency.

Tuning would be accomplished by altering the factor relating the Base Frequency to 1.0 Hz, and consonant transposition would be accomplished by altering the ratio relating the Anchor to the Base Frequency.

This seems a little cleaner to me than combining a Double and an integer ratio into a 'dual-component' type, but your mileage may very.

In any case, these details need not be exposed to the user! What matters is that the pitches of the fundamentals of the series composing the harmonic structure are tunable as a unit and editable by integer-ratio factors, collectively as well as individually, and that those pitches as well as that of the HCF are clearly specified.

Sunday, January 27, 2019

From Harmonic Structure to HCF to Sample Value, Part 4: Focusing on Phase & Phase Advancement

So maybe you're a little hazy on what is meant by phase, even more so regarding phase advancement, and not at all convinced I know what I'm talking about in suggesting that repeatedly multiplying the phase of a lower frequency by a positive integer can be used to generate a higher frequency. Like, how does that work?

Phase relates back to the sine wave, which itself relates back to the unit circle, but this is beginning to feel like a circular definition. What does it really mean?

Let's approach this from a different direction, using an analogy. Say you have a shaft, rotating at one degree per second. It's going to take that shaft 360 seconds to complete one rotation. Now say you have another shaft, the position of which is updated once per second according to the rule that its new position should be twice that of the first shaft. If the first shaft has moved 10 degrees, the second shaft will have moved 20 degrees. If the first shaft has moved 50 degrees, the second shaft will have moved 100.

But what happens when the first shaft has moved 180 degrees and the second shaft has moved 360 degrees? The second, faster shaft is already back where it started while the first shaft is still only halfway around. Fine, no problem, it's free to keep right on moving, starting a second rotation while the first shaft finishes its first, but because doubling the number of degrees the first shaft has turned will now result in a number larger than 360, we'll need to remove the first 360 degrees to bring the result into a range we can work with. So, essentially, when it gets to 360 degrees the second shaft resets to 0 degrees and keeps on moving.

Likewise, when the first shaft gets to 360 degrees, it also resets to zero and keeps moving.

But what if for every degree the first shaft moves the second shaft moves 5 degrees. The same principle applies, but because we're getting the position of the second shaft by multiplying the position of the first shaft by 5, it won't be enough to subtract 360 degrees after its first rotation, we'll need something that will work no matter how many rotations it has already completed. That something is modulo division.

In this example, after multiplying the position of the first shaft by 5 we'll take the result of that and apply modulo 360, to remove all of the full turns and leave only the amount by which the second shaft's new position exceeds a full turn. We could use the same approach for the first shaft, but in that case it's simpler to just subtract 360 degrees every time it completes a full rotation.

You may recall, in a previous installment I said that if you measure phase (rotation) in cycles, modulo division isn't necessary. This is because if we were to use modulo division in that case, it would be modulo 1.0, which is exactly equivalent to simply keeping the fractional portion of a decimal number and discarding everything to the left of the decimal point.

So, to ease back into more standard terminology, phase equates to how much the rotation of a shaft, at any given point in time, exceeds an indeterminate number of complete rotations. How far beyond the start/end point of a cycle it has progressed, and phase advancement equates to how much rotation occurs between one point in time and the next, one second and the next in the above example. It is a rate of change.

Note that in the above example we only applied phase advancement to the first shaft, to determine its phase at the next point in time, and used that to calculate the phase at the same point in time for the second shaft. The rate of phase advancement for the second shaft is only implied, never explicit.

Using this approach we might add a third shaft, applying the same multiplier to the phase of the first shaft as we did for the second shaft, and be confident that the second and third shafts would always be perfectly synchronized, rotating in lockstep.

A cycle is a cycle, whether it's a sine wave or a rotating shaft or the interplay of the tilt of Earth's rotational axis with its movement around the sun, creating seasons.

Phase is what portion of the next full cycle has been completed, and phase advancement is the rate of change of the phase, change/time. For a shaft, phase advancement is how fast it is turning. For a sound, phase advancement is its frequency, its pitch. For Earth's seasons, phase advancement is how quickly one passes into the next.

If you were confused before, I hope that you are now at least less confused.

Tuesday, January 22, 2019

From Harmonic Structure to HCF to Sample Value, Part 3: Clarifying Terminology

This is very much a work in progress. No doubt the list will grow over time, as inspiration strikes and I have time to give to it. Some items link to Wikipedia (or other) articles, and some of those might not be included except that the articles they link too are so well done and include relevant material.

Array
A common way of structuring data, a list of items, usually all of the same type.
Big O notation
A standard method of expressing the computational complexity of an algorithm.
ADSR Envelope
Attack: the initial, usually abrupt escalation of volume at the beginning of a note.
Decay: the rapid loss of some of that volume immediately following the attack phase.
Sustain: a period of more stable volume following the decay phase.
Release: the final attenuation of volume to zero.
Anchor
My name for an intermediary object used to establish the frequencies of the fundamentals of the harmonic series composing a harmonic structure, and the frequency of their Highest Common Fundamental. The frequency of the Anchor is specified by the combination of two factors multiplied together, a scalar and an integer ratio.
Base Frequency
My name for an intermediary object which may be used in conjunction with the Anchor, providing the scalar factor.
Beat Frequency
A periodic variation in volume at a rate that is the difference between the frequencies of two simultaneous tones.
C-family Programming Languages
For the present purpose, C, C++, and Objective-C.
Callback
Code you provide to a framework which it calls when the conditions are right or when the time comes.
CD Quality
Two channels of 16-bit integer values at 44100 samples per channel per second.
Consonance
A quality of "simultaneous or successive sounds...associated with sweetness, pleasantness, and acceptability" best exemplified by chords composed of frequencies all related by ratios of small integers.
Consonant Transposition
Moving a harmonic structure up/down-scale as a unit, by an integer-ratio factor.
CPU Cycle
Not exactly a precise unit of measure, because various instructions take differing amounts of time to complete, because multiple instructions may be 'in-flight' simultaneously, and because it is becoming increasingly common to offload much of the work to coprocessors better adapted for particular classes of algorithms. Even so, it still works as a rough measure of computational effort.
Cycle
One repetition of a repeating pattern or event.
Cycles per Second
The number of repetitions of a repeating pattern or event with each passing second.
Digital Audio
The encoding of audio signals into or their synthesis in digital form, subsequent processing, and decoding to analog signals to drive speakers.
Double Precision
A floating point number with relatively high precision, usually occupying 64 bits.
Floating Point Number
A means of expressing very large, very small, and fractional values.
Frequency
The rate of repetition of a repeating pattern or event; for sound usually expressed in cycles per second (Hertz or Hz).
Fundamental
The lowest member of a harmonic series, every other member of the series being an integer multiple of the fundamental.
Harmonic
A member of a harmonic series, an integer multiple of the fundamental.
Harmonic Number
An integer representing both the factor by which the frequency of the fundamental of a harmonic series is multiplied to produce the frequency of a particular harmonic and the position of that harmonic within the series, where the fundamental itself is the first harmonic.
Harmonic Series
A sequence of integer multiples of a fundamental, of a fundamental frequency in the context of sound.
Harmonic Structure
Two or more harmonic series the fundamentals of which are related by integer ratios, having members with the same frequency at different harmonic numbers (although these may occur at harmonic numbers too high for inclusion in a given implementation).
Hertz (Hz)
Cycles per second.
Highest Common Fundamental (HCF)
The highest frequency which can serve as the fundamental of a harmonic series including every member of every harmonic series constituting a harmonic structure.
Index (plural: Indices)
A means of specifying a particular member of an array.
Integer
A whole number: ..., -3, -2, -1, 0, 1, 2, 3, ...
Integer Ratio
A ratio in which both the numerator and denominator are positive integers. In the context of ratio-based music, ratios composed of small integers are strongly preferred.
Intensity
An abstract representation of volume, which may or may not scale linearly.
Inverse (multiplicative)
The result of reversing the numerator and denominator of a ratio.
Modulo Division
Extraction of the remainder from a division, as opposed to its truncation or expression as a fractional result.
Note
An instance of a tone, generated either programmatically or in response to a user event.
Phase
The state of completion of the current cycle of a repeating pattern or event.
Phase Advancement
The amount by which the phase changes between one point in time (one sample) and the next.
Pi (𝜋)
The ratio between the circumference and the diameter of a circle.
Pitch
Used interchangeably with frequency, but occasionally with the suggestion of subjectivity.
Radian (rad)
The angle traversed by wrapping the radius of a circle around its circumference; commonly used as the unit for an argument in functions that calculate trigonometric values.
Ratio (fraction)
A proportionality between two quantities, calculated by dividing one (the numerator or dividend) by the other (the denominator or divisor), using a variation on division that preserves any remainder as a fractional component of the result, for example a quotient of type Double.
Real-time
Any computational context where both the initiation and completion of a sequence of operations are time-constrained to the extent that efficiency becomes a high priority.
Sample
A single value, representing a single instant, in a sequence of values composing a digital audio signal.
Sample Rate
The number of samples per second composing a digital audio signal.
Secondary Harmonics
The harmonics of a member of a harmonic structure.
Sine
A repeating trigonometric function.
Sine Wave
A graph of the sine function, and, by analogy, any phenomenon having a similar pattern, like sound.
Sound
The sensory experience of a sound wave.
Sound Wave
Propagating variations in air pressure, or a graph of those variations.
Table
An ordered list of values of the same type, frequently implemented as an array.
Tone
Used interchangeably with frequency, but occasionally with the implication of a voice being applied to that frequency.
Truncation
Discarding the fractional portion of a floating point value, as when performing conversion to an integer. Also discarding the remainder in integer division.
Unit Circle
A circle with a radius of 1.0, frequently centered on the origin of a two-dimensional coordinate system (x = 0.0 and y = 0.0); the foundational concept for much/most of trigonometry.
Unsigned Integer
An integer with no sign bit, representing a value that is greater than or equal to zero.
Voice
Any attributes in the synthesis of a note other than its basic frequency and the overall volume, for example the ADSR Envelope or emphasis on different harmonics as the note progresses.
Zero-based Indexing
The first element of an array has index 0.

Feel free to comment with suggestions, terms to include and/or definitions, or if you disagree with a definition I've supplied. If I use a definition that you've supplied, I'll provide attribution by linking to the comment, unless you specify that I should not do so.

Sunday, January 20, 2019

From Harmonic Structure to HCF to Sample Value, Part 2: Multiples of Sine Phase Advancement per Time

Beginning in Part 5 of the previous series, I've already gone into some detail regarding what I've termed the Highest Common Fundamental (HCF). I may revisit this, but that existing explanation seems adequate for the present purpose.

The main reason for caring about the HCF, perhaps the only reason, is that it can be used to generate any tone in the harmonic structure associated with it. To achieve this, some conceptual agility is required.

The first step is to determine the position of the HCF relative to some reference which is generally stable with regard to the harmonic structure (the Anchor), expressed as an integer ratio, and to use that ratio to determine its frequency. Any change to the structure will necessitate recalculation of this ratio and the resulting frequency.

Next, that frequency is recast as a rate of sine phase advancement. The units for this are the same as for frequency, and, as mentioned in the previous installment, there are various ways of expressing this:

  • cycles per second (Hz)
  • cycles per sample
  • radians per second
  • radians per sample
  • sine table indices per second
  • sine table indices per sample

The default choice for specifying the frequency of the HCF is cycles per second (Hz), but those may not be the most appropriate units for specifying the HCF's rate of sine phase advancement. Let's take a closer look at how we'll be using that quantity.

When sound generation starts, we'll be setting the phase of the HCF in motion. For each sample, it will be advanced by an amount determined by the frequency. If that amount is expressed 'per sample' rather than 'per second' the advancement can be a simple addition, with a check for exceeding (>=) 1.0 cycles, 2𝜋 radians, or the number of elements in the sine table (and, if that check returns true, subtracting 1.0 cycles, 2𝜋 radians, or the number of elements in the sine table).

Since we'll be using the phase on a 'per sample' basis, let's remove the 'per second' options from the list, leaving us with:

  • cycles per sample
  • radians per sample
  • sine table indices per sample

To produce the contribution of a particular harmonic to a single sample, we'll multiply the phase (cycles, radians, or sine table indices) of the HCF for that sample by the harmonic number (in terms of the HCF) of the harmonic we want to generate, extract from that product just the portion by which it exceeds the nearest multiple of 1.0 cycles, 2𝜋 radians, or the number of elements in the sine table (modulo division, or the equivalent), and translate that into an index into the sine table to retrieve a sine value.

For phase expressed in cycles, instead of using modulo division, from the product of the first step above we can simply extract the fractional portion (x - trunc(x)), multiply that by the number of elements in the sine table, and truncate that result to produce a usable index.

For phase expressed in sine table indices, modulo division by the number of elements in the sine table is necessary, but once that's done a single truncation is all that's required to produce an index for table lookup.

Phase expressed in radians has neither of these advantages. It requires both the modulo division and multiplication by a conversion factor, followed by truncation, so let's eliminate it, leaving us with just two choices — cycles or sine table indices.

It comes down to which is more expensive (in terms of cpu cycles), modulo division or an additional truncation, a subtraction, and a multiplication. That seems like a pretty easy call, modulo division is probably several times more expensive than the combination of three fast operations. This might seem trivial, but if you want to be able to generate multiple simultaneous tones, each composed of multiple secondary harmonics, 44100 times per second, wringing out those extra cpu cycles becomes important.

So, the winner is phase expressed in cycles and phase advancement in cycles per sample.

Now that we have our units nailed down, let's make another pass through the context and the process of arriving at sample values. The Anchor is like a handle, a convenient point of reference which is nominally stable with regard to the Harmonic Structure, at least between changes to that structure. The Highest Common Fundamental (HCF) is a downward projection of the structure; it cannot be higher than the lowest fundamental of a harmonic series included in the structure, and would typically be even lower, very possibly subsonic. While its position is dictated by the structure, the HCF is defined in terms of the Anchor, by means of an integer ratio, which is used to determine its frequency, in cycles per second (Hz). That frequency is then re-expressed in terms of cycles per sample (simply divide by the sample rate in samples/second), which are also appropriate units for per sample phase advancement.

Everything up to this point is only done once, unless the harmonic structure itself is edited, in which case it is done again. What we now have is an HCF defining a harmonic series which includes every member of the harmonic structure, including all of their secondary harmonics, and a rate of phase advancement for the HCF. We could at this point, calculate rates of phase advancement for each member of the structure and launch separate phase tracking for each in response to user events, but this would result in entirely random interference patterns. So, instead, we will launch phase tracking for the HCF alone, and calculate phase alignment on a per sample basis for each member of the structure which is currently participating in sound generation.

Because we have chosen to express phase (advancement) in cycles (per sample), this calculation is as simple as multiplying the phase of the HCF for the current sample by the harmonic number of the member (in the harmonic series defined by the HCF), and keeping only the fractional portion of the resulting value, the part after the decimal point (x - trunc(x)). That fractional portion of the product is multiplied by the number of elements in the sine table, and the result of that converted to an integer using a truncating initializer.

This index is then used to retrieve a sine value from the table, which is then multiplied by a volume factor (calculated separately) for that member and the current sample, and these values for each of the currently participating members are added together to produce the overall sample value.

Note that the list of currently participating members and the volume factor for each must be maintained on a per-sample (or at the very least per-callback) basis, and either copied into the real-time context or modifications based on user events saved until the real-time callback is done with them, by means of some simple locking mechanism. Even this is probably best done in C or C++, with your Swift code passing in user events by calling C/C++ functions.

For best effect, you'll probably want to either insert an equalizer downstream or incorporate the function of an equalizer into the calculation of volume factors. The latter approach seems preferable, since it removes some load from the real-time pipeline, but equalizers and UI to match are readily available plug-ins, so that might be one optimization too many, at least initially.

That's it in a nutshell, although I may have more to say about specifics as I get further into it myself.

Thursday, January 17, 2019

From Harmonic Structure to HCF to Sample Value, Part 1: Laying the Foundation

I've glossed over this subject previously, but here I'll go into it with greater care, and in greater detail. The context for all that follows is computer software which generates sound (a sequence of sample values) on-the-fly, constrained both by the need to make them available within the time allowed and by the need to minimize the latency experienced by the user of the software, the delay in response to user actions.

So, what is pitch?

My take is that it's very nearly interchangeable with frequency, if perhaps slightly more subjective.

So what is frequency?

Frequency is the rate at which some specific type of event occurs, the number of events over a given time span, or events per unit time. The beating of your heart, for example, would ordinarily be measured in beats per minute. The speed of your car would (in the U.S.) be measured in miles per hour, and the speed of its engine would be measured in revolutions (of the crankshaft) per minute.

Pitch, the frequency of a sound, is typically measured in cycles per second (Hertz, or Hz in abbreviated form). A cycle is a single unit in the repeating pattern of a sine wave, a construct from trigonometry that provides a decent approximation for the manner in which sound is transmitted by successive waves of higher and lower pressure passing through air.

Trigonometry is based around the idea of a unit circle, a circle with a radius of exactly 1. We commonly think of circles as being divided into 360°, but in trigonometry it is more common to express the magnitude of an angle, arc (a partial circle), or rotation in terms of radians. A radian is the same length as the radius, but wrapped around the perimeter of the circle. A unit circle has a circumference of 2pi (2𝜋) or about 6.283185307179586 radians. (Radians are also commonly used in standard functions that compute sine values.)

One cycle of a sine wave is analogous to one complete rotation (2𝜋 radians) of a unit circle. In fact, if you were to roll a unit circle along a horizontal line and track the vertical displacement of some point on that circle by tracing the same vertical position along a vertical line through and moving with the center of the circle, the result would be a sine wave. If you can deal with the 3-dimensional projection, the following animated GIF is an even better visualization. (source: Wikipedia)

What is actually being traced in this animation is a cosine, but the shape is the same as a sine curve.

What all of the foregoing is leading up to is the point that, even in the context of sound, there are other valid measurements of frequency beside cycles per second. We might just as well express frequency in terms of radians per second, if there were any advantage to doing so, and there are even more options.

When dividing one cycle of a sine wave into smaller segments, with the intention of precomputing sine values at evenly spaced intervals and using these to populate an array for later access by means of index values, to avoid the performance hit of having to compute sine values on-the-fly, the number of segments used, which will also be the size of the array, is almost completely arbitrary, at least above a lower threshold at which the division into segments is fine-grained enough to produce acceptable fidelity. One might use 360 segments, or 44,100 segments, the same as the number of samples per second in CD-quality audio. Two other options, 256 and 65536 (2^8 and 2^16 respectively, where '^' is an exponentiation operator), are suggested by potential performance advantages around particular integer math operations. The only downsides to using more, smaller segments are the amount of fast memory consumed by the array, since it must remain in memory whenever sound generation is running, and the time and computation effort (battery power) needed to create the array, if you choose to remove it from memory whenever sound generation ceases.

From here on I will begin using 'array' and 'table' more or less interchangeably. Conceptually, the collection of sine values constitutes a table, but it is necessarily implemented as an array. In this context, both refer to a sequence of index-accessible sine values beginning with sin(0.0) and progressing at even intervals up to but not including sin(2𝜋).

As with various ways of delineating events to be timed, time need not be measured in seconds. Another altogether valid unit of time is the interval between successive audio samples, 1/44100 second in the case of CD-quality audio.

Taken together, the above offers us six different ways of expressing frequency, only one of which is cycles per second. Remember that a cycle is one complete sine wave, that a radian is equal to 1/2𝜋 cycles (about 0.159154943091895), and that the magnitude of sine table indices as a measure of a fraction of a cycle depends upon the size of the array holding the sine values.

  • cycles per second (Hz)
  • cycles per sample
  • radians per second
  • radians per sample
  • sine table indices per second
  • sine table indices per sample

Which of these is chosen has implications for the complexity and performance of the algorithms involved, which will be the subject of the next installment.

Monday, November 12, 2018

RatioKey project posted to GitHub

I've placed the Xcode project for RatioKey 1.1 in a GitHub repository.

This is an outdated Xcode project, unchanged since late 2010. Attempting to load it into a modern version of Xcode results in many errors.

Casually reviewing the code, eight years on, I'm reminded of the painful effort involved in getting it into a shippable state, and am mildly horrified at the needless complexity it oozes. Brother Ockham would not be pleased.

Friday, October 26, 2018

Pursuing clarity through openness, part 9: crafting voice by shifting amplitude among harmonics over time

Just as every member of the harmonic series composing a structure is also a member of the harmonic series defined by the Highest Common Fundamental, so too are their own harmonics, and we can make use of this to transform them from simple sine waves to complex tones, perhaps even phonemes, by specifying how much each of those secondary harmonics should contribute to voicing the primary harmonics in response to user actions.

A fairly simple and straightforward way of doing this can most easily be described by analogy to a row of decrepit fenceposts and the nonparallel rails (or wire, if you prefer) between them. The fenceposts represent particular points in time, specified for the purpose of rendering in terms of samples, strung out between the beginning and end of a note. The rails represent an intensity (volume factor) for each of the secondary harmonics contributing to the overall sound to be rendered, but unlike a fence in good repair these rails may cross each other and either end (or the entire rail) end may lie on the ground between any pair of posts. Typically, the rails will at least sit at an angle between any two posts, representing interpolated intensity values.

What is really at play here is the movement of acoustic energy among secondary harmonics in the interest of creating the voice of a primary harmonic. Because those secondary harmonics are also part of the harmonic structure, the same process of multiplying the phase of the Highest Common Fundamental (for the current sample, expressed in cycles) by the harmonic number in terms of the HCF for those secondary harmonics, keeping only the fractional part, multiplying by the size of the sine table, and truncating to produce an index for sine lookup, still applies. In fact this replaces going through these steps for the primary harmonic, since its voice is now composed of the intensities of its own harmonics, remembering that it is its own first harmonic.

For each sample and each secondary harmonic, the result of the sine table lookup is multiplied by the intensity factor calculated for that secondary harmonic and that sample, then the results of those multiplications added together to arrive at the contribution that note makes to the overall sample value. These totals for multiple simultaneous notes are simply added together.

It might be more efficient to combine intensities for various HCF-harmonics (multiple instances of the same pitch originating from different locations within the structure) before multiplying by the result of the sine table lookup, rather than doing this once per sample for each of them, but that is a more complex coding problem, so I'll leave this as an mission for the reader, should you decide to accept it.

If the fencepost and rails analogy doesn't work for you, and you have an old-style bulletin board and some push-pins and string handy, you can use columns of push-pins to represent posts (samples for which the intensities of secondary harmonics are explicitly specified) and string stretched between those push-pins to represent interpolated values.

More elaborate versions, using graphically defined Bézier curves or explicit functions to specify the per-sample intensities of secondary harmonics are also options. So too are modifications to those specifications based on user action parameters like the velocity, pressure, and time-to-release.

Okay, take a breath, step back, let it sink in, and see if a playground full of cool toys doesn't gel in front of you, and maybe also some appreciation for why a part-time developer like myself might find such a project daunting (as well as chronically engaging), and why I have chosen to lay it all out.

Even this isn't exhaustive; there's plenty of room for expanding upon this vision, and I invite any with the motivation to do so to take it and run with it.

I'm sure I'll have more to say, details to be filled in, loose ends to be tied up, but this marks the end of of my whirlwind introduction to the topic.

Back to Harmonic Lattice

Wednesday, October 24, 2018

Pursuing clarity through openness, part 8: integer-ratio intervals against a logarithmic scale

While integer-ratio intervals generally sound better than irrational intervals, the sensation of pitch is roughly logarithmic. Ascending octaves on a piano sound like they each rise about the same amount, but the frequency doubles from one to the next.

To accommodate this, when adapting harmonic series to a playable interface, whether on-screen or physical, the spacing between the buttons or touch-pads or whatever represents notes should diminish moving from lower to higher harmonics. The distance between the 1st and 2nd harmonics should be the same as the distance between the 2nd and 4th, between the 4th and 8th, and between the 8th and 16th. Depending upon how many harmonics you include, it may not be possible to have them all on-screen or within reach simultaneously, and the higher ones will present increasingly smaller targets.

The problem of smaller targets can be alleviated by using multiple harmonic series, which is what harmonic structures are all about. It can also be alleviated by removing harmonics that are irrelevant to a particular purpose, leaving a sparse structure that might be termed a lattice. This filtering is another case where prime factors can be useful.

A perk of using a logarithmic scale for pitch is that it allows having multiple harmonic series that are copies of a template, all with exactly the same dimensions. These duplicate series can be moved up or down-scale without distorting the correlation between their position and the frequencies they produce. Even better, everything at the same position along that scale will have the same pitch.

I'm rather fond of the notion of a physical instrument interface patterned generally on the shape of the saguaro cactus, which has branches that emerge almost horizontally from the main stem and then turn sharply upwards. Vertical pieces each representing a single harmonic series could be mounted on a central post so they would slide up/down through slots, or pivot on a parallelogram linkage, the idea being that the higher they were positioned the higher the frequencies they would generate, again using a logarithmic frequency scale.

There is one more major topic to cover, and probably some loose ends to tie up, but I think I'll be taking a break before proceeding with the next installment.

Part 9: crafting voice by shifting amplitude among harmonics over time

Pursuing clarity through openness, part 7: from structure to sound

Digital sound is a complex subject, with many variations on the theme. Most use Pulse-code Modulation (PCM) in some fashion. PCM is a sequence of numbers representing the amplitude of a sound wave, the instantaneous pressure, measured frequently at regular intervals in the case of a microphone capturing sound from the environment. The frequency of those measurements, the sample rate, is most commonly 44100 per second, too low to capture the nuances of the squeaks made by mice and bats but more than adequate for human voices.

The way those measurements are encoded varies, with 16-bit signed integers being a common format made popular by its use on CDs. Apple uses that format for its microphones and speakers, but internally Apple's OSes use 32-bit floats to pass data around, waiting until the very last step to convert those to integers for output. So, at least for Apple devices, synthesizing sound means generating a sequence of 32-bit float 'samples' quickly enough to stay at least a little ahead of the output, so that it never runs out.

However, if you're working in an interactive context, where the delay between a user action and the sound it generates needs to be imperceptibly small, you don't want to get too far ahead of the output. If, for example, the length of a note depends on the time between touch down and touch up events, it cannot be entirely precomputed, and even if it could, if you want to be able to overlap multiple notes of the same pitch, there would still be the issue of combining them into the stream of sample values in a manner that produces proper phase-alignment, to avoid randomized interference phenomena.

The most straightforward approach is to generate the stream of samples to be fed to the output on the fly, just in time. Apple's older Core Audio framework provides callbacks for this purpose; you supply a block/closure (or a pointer to a function) to an audio unit, which then calls that code whenever it's ready for more data. This is a low-latency process. The challenge is to craft code that will return in time, so you don't leave the output without data. You stand a better chance of doing this in C than in Swift, but even in C you need to be careful not to try to do too much in a callback; anything that can be precomputed should be.

AVAudioNodes provide callbacks, but it's not clear to me whether these are appropriate for an interactive context. AVAudioNodes also wrap AUAudioUnits, which have callbacks of their own. I think it should be possible to make use of these and avoid the need to set up an audio unit in C, but I already had that code so I haven't yet put this theory to the test.

At this point you'll be staring, figuratively if not literally, at an empty callback routine. At least in the case of Core Audio audio units, you will have been passed a structure with a pointer to an array of pointers to buffers. Assuming only a single channel, you'll get the pointer in the [0] cell of the array and begin writing sample values into the buffer. When done, you return the structure. Anything requiring continuity from one such call to the next, such as phase alignment, will need to have broader scope than the callback routine (in the case of a named function, static variables might work).

The samples we'll be adding to the buffer mentioned above will be derived from sine values. Because sine values take some effort (cpu cycles) to compute they should be precomputed, so we'll want a table (an array) of them from which particular values can be extracted using a simple index. The table should represent one complete cycle of a sine wave, from sin(0.0) up to but not including sin(2.0 x pi) assuming you're using a sine function that takes radians as an argument.

Frequency, also called pitch, can be expressed in terms of the rate of traversal across the range of indices into this sine table, measured in indices per sample. Walking off the end of that range and going back to the beginning equates to completing a single cycle of a sine wave. When using this approach, the size of the sine table (the number of elements it contains) becomes an important component of the calculations. The larger the number of elements in the table, the more precise the values provided by table lookup will be, but I consider 65536 (2^16) a practical upper limit. Any table size that is an even power of 2 allows moving back from the end to the beginning in an efficient manner.

I've managed the business of tracking the phase-alignment of a synthetic sound wave as a progression through a sine table several different ways. Originally I used radians/second to represent frequency, which meant that the phase-alignment for the current sample had to be multiplied by the sine_table_size/two_pi and the result of that truncated to produce an integer index. Then I realized I might just as well be using sine-table-indices/second, which only needs to be checked for being out of range and adjusted by sine_table_size if it is. At some point it occurred to me that this approach, if combined with a sine_table_size equal to the sample rate, would eliminate the need for converting from cycles per second to sine-table-indices per second, since they would be equivalent, requiring only a type conversion from double to int just before the table lookup.

(Note: the above paragraph and the two that follow are a bit confused, but that's consistent, since so was I while stumbling through this transition in algorithmic approaches. In any case, straightening this out is more than I can do at this moment. Just remember that dimensional analysis is your friend! 14Jan2019)

When I began to experiment with complex tones, I also began to use the current phase-alignment of the fundamental to generate the phase alignments of any harmonics to be included, multiplying it by their harmonic numbers, applying modulo division by the sine_table_size to reduce this product to the proper range, and using the result of that modulo division as an index into the sine table. At some point in 2017 or early 2018, it occurred to me that this same approach would work with harmonic structures composed of multiple harmonic series, if I were to track the phase of the Highest Common Fundamental (HCF) and multiply that by the harmonic numbers of the members of the structure as determined by their position within the harmonic series defined by the HCF, their HCF harmonic numbers.

Then, finally, I realized that, if I were to keep frequency in cycles per second, I could eliminate the modulo divisions, since modulo 1.0 is equivalent to simply dropping the non-fractional part of a floating point value. The tradeoff in doing this is the need to reintroduce multiplication by sine_table_size followed by truncation to produce integer indices for table lookup.

By this time I'd lost track of the distinction between these various approaches and began to combine elements of them inappropriately, leading to confusion – vaguely analogous to random mutation in genetics, it occasionally works out but mostly you get no discernible difference, or monsters.

So, now you at least know that there can be several ways to specify a frequency, and probably also have an inkling that the choice of which to use impacts the manner in which the phase is tracked and converted into sine values, which then feed into the generation of sample values.

That's probably enough for one day.

Part 8: integer-ratio intervals against a logarithmic scale

Tuesday, October 23, 2018

Pursuing clarity through openness, part 6: the anchor, HCF, and tracing the chain of tonality

From this point on I'll (mostly) refer to the Highest Common Fundamental as HCF.

While you might actually choose an anchor that initially matches the pitch of the HCF, they have different purposes, and different behavior resulting from changes to the structure. The anchor is like a handle, a means of hanging onto the structure as a whole, and exactly where it's positioned is somewhat arbitrary; it is free to remain stable, or not, in the wake of editing of the structure. The HCF, on the other hand, even though it will end up being defined in terms of the anchor, is not arbitrary at all. It's position is dictated by the fundamentals of the series composing the structure. Change one of those fundamentals or add another series to the structure and it's likely that the HCF will also need to change, and its relationship to every member of the structure along with it.

The anchor is the only component of the structure which is directly tied to the pitch-scale; everything else is connected to the pitch-scale through the anchor. For the purpose of sound generation that connection also passes through the HCF. Starting with the ratios relating the fundamentals of the series composing the structure to the anchor, expressed in lowest terms, the denominators of those ratios will need to be reduced to their prime factors, for example 1/6 becomes 1/(2 x 3) and 1/9 becomes 1/(3 x 3). For each prime used, the greatest number of times it is used in a single denominator (in this example 2 and 3 x 3) is included in a multiplication to produce the denominator of a ratio relating the anchor to the HCF (2 x 3 x 3 = 18, yielding a ratio of 1/18). This is the application of prime factors for which I can make an argument in favor of their relevance.

To determine which members of the harmonic series defined by the HCF represent the fundamentals of the series composing the structure, we'll divide the ratios relating those fundamentals to the anchor by the ratio relating the HCF to the anchor, or rather invert and multiply (1/6 x 18 = 3, and 1/9 x 18 = 2).

At this point it will be helpful to introduce some shorthand notation, to differentiate between native harmonic numbers (multiples of the fundamentals of the harmonic series composing the structure) and harmonic numbers in the series defined by the HCF, I'll prefix the latter group with "HCF-" (HCF-3 and HCF-2 in the above example).

To go on to determine which members of the harmonic series defined by the HCF represent the members of the other series, we'll multiply their native harmonic numbers by the HCF harmonic number of the fundamental of the series to which they belong (using the 5th harmonic in each case, 5 x HCF-3 => HCF-15 and 5 x HCF-2 => HCF-10).

We can carry this one step further, crafting complex tones using harmonics of the harmonics, which are also part of the structure, by simply adding another multiplication factor. Continuing with the above example, the 2nd harmonic of the 5th harmonic of each series will be two times the basic harmonic (2 x HCF-15 => HCF-30 and 2 x HCF-10 => HCF-20) and the 3rd harmonic of the 5th harmonic of each series will be three times the basic harmonic (3 x HCF-15 => HCF-45 and 3 x HCF-10 => HCF-30).

Note that it's quite possible to arrive at multiple instances of the same HCF harmonic, and there may be algorithmic efficiencies to be built around that.

What we now have is a chain of connection, beginning with the pitch-scale, passing through the anchor, through the HCF, to the fundamentals of the harmonic series composing the structure, and from them to the other members of those series, and from them to their own harmonics, expressed in terms of multiples of the HCF.

So how do we get sound out of this? That'll be next.

Part 7: from structure to sound

Monday, October 22, 2018

Pursuing clarity through openness, part 5: the highest common fundamental

Given a harmonic structure, two or more harmonic series all connected together through identically pitched members at different harmonic numbers, an anchor having nominally stable integer-ratio relationships to the fundamentals of those series, and a means of specifying the pitch of that anchor, what more do you need?

Perhaps nothing else, depending upon what you have in mind to do, but if you intend to use this assemblage to synthesize sound there's another important piece to the puzzle.

Say you have two tuning forks that are very nearly the same pitch, within a fraction of 1 Hz of being exactly the same, and you strike them both and hold them near each other and close to one ear, the sound you hear will rise and fall as the sound waves from the tuning forks alternate between constructive and destructive interference. The rate at which this happens is the beat frequency, and the closer the tuning forks are to being exactly the same pitch the lower the beat frequency.

With digital sound, it is quite possible to have two sound sources at exactly the same constant pitch, meaning that how they interact at first is how they will continue to interact for as long as both sources persist, whether constructively, destructively, or something in between. What sort of interference you get depends entirely on the relative alignment of their phases at the outset, something that isn't easy to control if, for example, you're passing user events to code that spawns a new render thread for each new note. That scenario will reward you with random results.

To backtrack a bit, a latent property of harmonic structures is that they always imply a harmonic series that includes all of the members of all of the harmonic series composing the structure. The fundamental of that implied series will generally be lower than any of the fundamentals of the series composing the structure, although it might be the same as the lowest of them. In reality, there are many such fundamentals, since, if x defines a harmonic series which includes all the members of a structure, so too will x/2, x/3, x/4, and so forth. To keep things simple, what we really want is the highest pitch satisfying the requirement that it define a series including every member of the structure. This is what I'll call the 'Highest Common Fundamental' (HCF).

You can think of that highest common fundamental as a continuous sine wave — a constant tone, albeit one which may be well below the threshold of human hearing, and, in any case, doesn't participate directly in the sound produced. It serves a function vaguely analogous to that of a metronome, establishing a phase alignment that advances at a steady pace and can be used to drive the phase alignments of all of the members of the structure, enabling control over how they interact, whether constructively or destructively. I'll save the details of how this works for later.

For now just soak in the idea that every harmonic structure implies a fundamental defining a series that includes every member of the structure, and that, by providing a common basis for phase alignment, this implied fundamental can be used to drive sound generation while controlling for constructive vs. destructive interference.

Part 6: the anchor, HCF, and tracing the chain of tonality