Monday, February 5, 2024

The Modulation bar's new Targets pane now supports editing, but only to a limited extent, and here's why

The latest version of Polymeter adds an optional Target pane in the Modulations bar. This new pane also supports editing of the targets, to a limited extent. The table below shows which types of editing the Target pane supports.

EditSupported
Change type or targetYes
InsertNo
Insert GroupYes
DeleteYes
Cut/Copy/PasteNo
SortNo
Drag ReorderNo

Astute readers will notice that Delete is supported, but not Insert. This is most unusual and deserves some explanation. Also, Insert Group is supported, but not Insert. What is so special about Insert?

Let’s start by clarifying the semantics of the Insert command. The essence of this command is that it inserts something empty. For example, in a spreadsheet, the command typically inserts an empty row or column. In a text document, the command might insert a blank line. The idea is that Insert creates space for something, the contents of which will be specified later.

In the Modulation bar’s Source pane, the Insert command has the expected behavior. It inserts a modulation with the source track set to none. This means that the modulation does nothing, because it’s not connecting anything yet. In other words, the Insert command effectively inserts an empty modulation. Space is allocated for a new modulation, but further action is required to make the modulation functional. The user must select a source track other than none, and may optionally also change the modulation type.

Another important piece of background is that Polymeter’s data architecture is asymmetrical with respect to modulations. A track contains a list of the tracks that modulate it, but conversely, a track does not contain a list of the tracks it modulates. In the language of signal processing, a track maintains a list of its sources, but not its sinks. Sink here is synonymous with target. The reason for this is that the sequencer only ever needs to know which source tracks are modulating the track it’s working on. It doesn’t care about targets.

If we think of modulations as a tree-like hierarchical network, we can say that the sequencer traverses that network in only one direction, from the root towards the leaves. Because the sequencer never needs to work backwards from leaves towards the root, it can get away with keeping track of sources but not sinks. This data structure is known as singly-linked, versus doubly-linked, and it greatly simplifies the architecture. It also speeds performance, and avoids many bugs that can arise from so-called double bookkeeping. A modulation consists of one and only one element within a track, so there’s no ambiguity: it either exists, or it doesn’t.

With all that in mind, we return to the mysterious absence of the Insert command from the Modulation bar’s Target pane. Let’s consider an example. We have two tracks, called A and B, and we want A to modulate B. In other words, A is the source and B is the target. One could imagine adding this modulation to A, but as explained above, that’s impossible, because a track doesn’t list its targets; instead, it lists its sources. Our intention must be reversed: a modulator must be added to track B, referencing track A as its source. Track A is not edited at all. The target track is edited, not the source track. (see illustration)

Now try to imagine the following hypothetical situation, in which the Target pane does support Insert. We select a track, because we want it to target another track, but we don’t know which track it’s supposed to target yet. So we’re adding an empty modulation. In the Target pane, we select Insert. As we observed above, our selected track (the source) will not be modified. Instead, the target track must be modified. But since the target hasn’t been specified yet, where shall we put the empty modulation? The answer is: nowhere. We can’t insert an empty modulation in the Target pane because there’s nowhere to put it, and this is why the Target pane doesn’t support the Insert command. In order to insert a modulation in the Target pane, we need to know what the target is beforehand. That’s why Insert Group is supported: before inserting anything, it displays a modal dialog prompting the user to select the target track(s).

Thursday, January 25, 2024

The latest version of Polymeter adds a long-overdue feature: the Modulations bar now optionally shows targets as well as sources. This can be very useful for complex modulation networks. Until now, the only way to see the modulation targets of a track was via the Graph bar, with the graph’s Scope set to Targets or Bidirectional. The Graph bar requires installing Graphviz, so users who didn’t do that had no way of viewing modulation targets at all. But even if they did have Graphviz installed, the Modulations bar is easier to use, and it’s also the obvious place to look for this information.

To use this new feature, select Show Targets in the Modulation bar’s context menu. This command toggles the visibility of the Targets pane, in other words it shows the pane if it’s hidden, or hides it if it’s showing. When the Targets pane is shown, the Modulations bar is split into two panes, separated by a draggable splitter. The window is split horizontally by default, but if you prefer a vertical split, right-click the splitter to show its context menu, and select Vertical.

You might wonder why this feature wasn’t added sooner. It’s actually a challenge to implement, because the application’s architecture has a fundamental asymmetry between modulation sources and targets. Basically the issue is that modulations are singly- rather than doubly-linked. Each track keeps a list of the tracks that modulate it, i.e. its sources, but a track does NOT keep a list of tracks that it modulates. It could, but it doesn’t, and that’s because the sequencer engine—the part of the app that actually sequences MIDI, as opposed to mere user interface code—doesn’t care about modulation targets.

When the sequencer processes a track, it asks the question: “do any other tracks modulate this track?” In other words, it looks at the track’s modulation sources, also known as modulators. The sequencer never asks the question: “what tracks does this track modulate?” The sequencer’s top-level loop completely ignores modulator tracks, because they don’t output MIDI events, by definition. Modulator tracks are only accessed while processing a non-modulator track. More formally, the sequencer iterates modulation sources, but never iterates modulation “sinks”, and this is the reason for the architectural asymmetry.

The primary constraint on the design of the Polymeter application is optimizing the sequencer to maximize speed and throughput. Doubly-linked modulations would not help the sequencer at all. A few aspects of the user interface might benefit from doubly-linked modulations, but this is outweighed by much more complicated code, and the risk of many new bugs, particularly in the area of undoing edits.

So how do we answer the question, “what tracks does this track modulate”? We need that information in order to show the modulations graph, and we also need it to show targets in the Modulations bar. It’s done by creating a special type of lookup table called a cross-reference. To build the cross-reference, we iterate all of the tracks, and for each track, iterate its modulators, if any. While doing that, we put the modulators in “buckets” that correspond to target tracks. That might sound slow, but it’s the kind of thing computers are good at. Also, that process has been greatly optimized in this version, by storing the cross-reference in a “flat” one-dimensional array. Previous versions stored the cross-reference in an array of dynamic arrays, but this incurs significant performance costs, due to the potentially large number of arrays that must be reallocated and copied as they grow.

Stress testing was done to verify that the new modulation targets cross-reference consistently improves performance (it does). The second image in this post shows the stress test in the Graph bar, rendered by Graphviz, which after many years of faithful service continues to be one of my favorite open-source software projects ever.

Monday, December 26, 2022

Version 1.0.12 of Polymeter lets you specify step durations as fractions

Version 1.0.12 of Polymeter is released today. One big change is that in the track view, the step duration (AKA Quant) can now be entered as a fraction, instead of in ticks. An option was also added to display quants as fractions, and show a drop-down list of common quants during editing. Please download the new version HERE. The quant fractions are visible in this screenshot.

If the new "Show Quant as Fraction" option is true, quants are shown as fractions of a whole note; otherwise, quants are shown in ticks. For example using the default timebase of 120, a quant of 120 is shown as 1/4 (a quarter note), 60 is shown as 1/8, and so on. If you're using common quants, fractions are easier to work with than ticks, because the numbers are smaller and the application does the tick arithmetic for you. You can always enter a quant as a fraction, regardless of this option; this option only controls how quants are shown.

Some quants may still be shown in ticks even when this option is enabled. If a quant can be converted to a fraction having a denominator that's either a power of two, or three times a power of two, and the denominator is reasonably small, the quant is shown as a fraction, otherwise it's shown in ticks. For example you may enter the fraction 1/5, but it will be shown in ticks, because the denominator is non-conforming.

If this option is enabled when you edit a quant in the track grid, a dropdown list containing common quant fractions is displayed next to the edit box. To select a fraction from the list, either left-click it, or use the up and down arrow keys to scroll to the desired fraction, and then end the edit. If the desired fraction isn't in the list, type the fraction in the edit box, as two integers separated by a slash. You can alternatively type a single integer, in which case it's interpreted as a value in ticks. Fractions are reduced automatically, for example if you type 6/8 it will be shown as 3/4. If a given fraction can't be exactly converted to ticks, the closest value is substituted.

Sunday, July 31, 2022

Polymeter MIDI Sequencer / Command Line

I've created a command line fork of the Polymeter MIDI Sequencer, with all user interface code removed. This Visual Studio 2012 project builds a Windows command line application which opens and plays a Polymeter (.plm) document. The application can also export the document to a standard MIDI file. Removing the user interface greatly reduces the number of lines of code, which could facilitate porting the sequencer to other platforms. Be advised that this project still depends on MFC, and on the Windows API, and particularly on the MIDI streams API. The MFC dependencies include CArray, CMap, CString, CFile, and CStdioFile, and while these could in theory be replaced with standard library classes, this is a task best suited to a standard library expert, which I'm surely not. The repository is HERE.

Wednesday, March 30, 2022

Demons In My Head

My first vivid memory of NCP [No Commercial Potential, a radio program at WZBC 90.3 in Boston] is of hearing the soundtrack and dialogue of “Apocalypse Now” on my car radio while driving on Storrow Drive at night. I remember being struck by how much more spooky and atmospheric the dialogue was in isolation, without the film’s visuals. I started tuning in more regularly and became more aware of sound collage as a specific genre. I was making experimental music in the late 1980s, and playing guitar and singing in a couple of psychedelic hardcore bands (Iron Kilbasa, Oracles). I vaguely remember Oracles playing a live show at ZBC. Digitally delayed screaming and feedback solos were already routine features of my life. NCP acted like a seed, crystallizing disparate threads of my life into a more coherent whole.

In 1993, Suzan Baltozer was curating an art show called Angels and Demons at U. Mass Boston, and commissioned me to make a soundtrack for it. The result was a 40-minute sound collage called “Demons In My Head.” I had recently acquired a portable DAT recorder and a small stereo microphone, and busied myself exploring the soundscapes of my hometown.

The opening sequence results from me standing casually inside the I-90 tunnel under Massachusetts Avenue, with my microphone pointed towards the oncoming traffic. Such behavior would be almost unthinkable today. I reached out to many Boston area factories and some of them actually responded. By far the best was the Necco candy factory. They had astonishingly complex machines that looked like they’d been invented in the 19th century. The noise was deafening, but beautiful too. The machine that writes cute messages on candy made an especially intriguing sound. I also sampled the New Balance factory, and the gigantic printing presses of the Boston Globe, along with many public spaces such as the Harvard Square Red Line station and a carnival held near Kendall Square, but most of my samples came from around the house: washing machine, sink, water cooler, furnace, oil tank, and of course the toilet.

The sound collage was scripted using a technique I borrowed from David Lynch. I wrote the name of each sample along with a brief description on an index card. The cards were then arranged on the floor, so that their order could be changed easily. This was necessary because the sounds would be assembled on eight track quarter-inch reel-to-reel tape, with no possibility of changing the order afterwards. The index cards could also be arranged into multiple rows, making it easier to conceptualize the overlapping of sounds with each other, or with musical interludes.

I was so pleased with the result that I had it pressed as a CD, my very first musical release. At some point I got invited to play part of the CD on Andrew’s show. I’m sorry I don’t remember his last name, but he was infamous and later got banned from the station. I remember him referring to the Boston rush-hour traffic as a “sea of miserable faces.” Around that time I also put a copy of the CD in the mailbox of every NCP DJ. The results were mixed, but one DJ who really appreciated it was Gary Geiserman. I gather he got a lot of mileage out of that toilet flush. He later invited me onto his show, New Metaphysics. Gary’s inventive mashups of sounds from his suitcases full of cassette tapes influenced me profoundly, so much so that I subsequently wrote a software program (called Mixere) whose only purpose was to emulate his methods in the digital domain. But that’s another story for another day.

In case you weren't one of the lucky recipients of "Demons In My Head," or your copy wore out from overuse, you can download it in its original 16-bit glory from archive.org.

Monday, February 21, 2022

Why I'm not porting Polymeter

Milkii Brewster on GitHub asks "Linux version? Plugin version? Possibly maybe in the future?" and here's my answer:

The Polymeter source currently consists of 47,100 lines of C++ code. Of that, 80% is UI. How do I know that? To facilitate automated testing, I created a command line version of Polymeter, which can open and play a .PLM (Polymeter) document, and optionally export it to a standard MIDI file. After all of the UI is eliminated, 9,447 lines of code remain, and if you were planning to port the project, that would be the right place to start. The UI code is so dependent on MFC (Microsoft Foundation Classes) that it would almost certainly be easier to throw it away and start from scratch.

The sequencer core also depends on MFC, though far less so: it only depends on CString and the CArray and CMap container classes. It’s on my to-do list to make a fork of the command line version that uses the standard C++ library only. That means creating wrappers that emulate the interfaces of CString, CArray, and CMap, and that’s a much bigger job than it sounds. You might think someone would have already done that work, and perhaps they have, but if so they didn’t advertise it very well.

Even supposing those wrappers already existed and worked reliably, my application still depends on the Windows API. In particular, the sequencer is designed around the MIDI Stream API. Equivalent APIs may exist in Linux or MacOS, but even seemingly small differences between those hypothetical APIs and the Windows API could present huge obstacles. I surmise that it’s possible, because my program works correctly under Wine. I’ve never studied the Wine MIDI Stream code, but I wouldn’t be surprised to learn that it’s messy. It’s solving a hard problem that involves multithreading and device drivers.

But these are just technical issues. I’ve done plenty of porting, for example during my professional career I ported software from Windows to embedded processors, so I have a good idea what’s involved. The real issue isn’t technical, it’s temporal. I’m old! I’ve been working on the modern (Windows) version of this project for three years already, and I started the original version in 1998 under MS-DOS. I probably don’t have enough time left in my life to learn UI programming for MacOS or Linux or WxWidgets or Qt, certainly not at the same level of expertise I already have with Windows and MFC.

I didn’t write a polymeter sequencer out of curiosity, I did so because I wanted to compose in complex polymeter. I had hoped to avoid spending so much time writing software. I tried many commercial products before undertaking the Windows version of Polymeter, but sadly none of them had significant support for complex polymeter, and certainly not for polymeter modulation, many types of which I have—as far as I know—invented. I prefer to spend my remaining years actually using my sequencer, which I’ve paid so dearly for, to compose novel and aesthetically pleasing music. Other than composing persuasive examples of complex polymeter, probably the best use of my time would be to write a paper about all this. That’s also on my to-do list.

But even if I had another life to lead, why would I spend it on porting when perfectly good solutions already exist? There’s no significant obstacle to you or anyone else running Polymeter. You could simply obtain a Windows computer. It needn’t be anything fancy. A used PC would set you back a few hundred bucks, surely insignificant compared to thousands of hours of development time. Or you could use PlayOnMac or Crossover or Wine itself to run Polymeter under Linux or MacOS. Sure there are a few issues running in Wine, but 99% of the program works fine. Or you could run a VM, or do dual boot. So in short, the answer is, are you volunteering to port the project? If so, great. Otherwise, it is what it is.

Wednesday, December 22, 2021

Polymeter Phase diagram with Convergences

Version 1.0.4 of the Polymeter MIDI Sequencer features a couple of cool enhancements to the Phase bar, both accessible via its context menu. The first is that the phase diagram can now indicate convergences slightly before and after they occur. The participating orbits are increasingly highlighted as the convergence approaches, and gradually return to normal as the convergence recedes. This is useful because the planets often move fast enough that it can be hard to tell when they’re lined up. Also the planets can appear lined up when they’re actually not, unlike the convergence indicator which is foolproof. And, because the highlighting changes both the width and the color of each orbit in proportion to its proximity to convergence, it’s very satisfying to watch. The second feature is that the Phase bar can now go full screen, without caption bar or borders, which is similarly visually pleasing. Music: "Atunwi" from "Polymeter" (Mental Groove). More info: https://victimofleisure.github.io/

 


 

The Modulation bar's new Targets pane now supports editing, but only to a limited extent, and here's why

The latest version of Polymeter adds an optional Target pane in the Modulations bar. This new pane also supports editing of the targets, to ...