There's a number of scale remappers for Max. This one is optimized for real-time performance. It prebuilds 160 pitch remap lists during load. Subsequently, only one lookup operation on the selected pitch, key, and transposition is required during performance.
1. Design Notes
The table data is stored in a COLL object, which is set to regenerate on each load to reduce any requirement for external data files, but it could be stored once and reloaded to improve load time slightly. Double=clicking on teh COL object in a locked patch displays the generated data.
Rather than access the COLL object directly, the key and scale controls select one of the data rows in the COLL object and load it into a ZL REG object. This removes one additional index lookup during play. The transposition is then performed by rotating the currently selected lookup table. By this method transposition remains in the currently selected key and scale.
A small amount of additional processing is added in this demo implementation to update the output pitch when the panel controls change, which typically would not be needed during MIDI pitch processing, as the pitch has already passed through, the resulting new transposition would not affect the playing note.
2. Design Variations
The pitch remapper is based on the design from the ULA ring messaging system, published on Yofiel three years ago. Most significantly compared to the original design, this version eliminates a modulo and integer divide on each MIDI note, which remain the most computationally expensive CPU operations besides transcendentals.
The Godel 3.1 instrument (currently in alpha) supports transpositions of up to 6 octaves in each direction. Rather than extend the list length by 120 entries, Godel does use a modulo operation, but the modulo is on the transposition input, rather than the note itself, which is much more efficient. Also, rather than perform an integer divide, it subtracts the modulo from the pitch transposition, then adds that result to the rotation output with a VEXPR object. The list output passes to a 'ZL LOOKUP' object on the pitch path in the container patch, so that the pitch signal does not generate messages at patcher inlet/outlet boundaries. At that level, the lookup is followed by a 'CLIP 12 132' to prevent notes at range boundaries from encountering the wrapped values by the rotation. This MAX enhancement, together with others described on this site, reduced typical processor load during rapid arpeggiations by 5-10%, as well as reducing peak CPU utilization.
A demonstration patch is available in the Synthcore2 bundle.