Hello,
I wanted to draw only the note names of scales on the grid of a semitone axis, and after some fiddling I came up with the following rather compact combination of the classes Warp, Scale and ControlSpec. Maybe it can be helpful to someone? Comments are welcomed.
First, make these two definitions in a suitable .sc file:
ScaleGridLines : AbstractGridLines {
// There is another, similar midinote method in the SC3-plugins,
// but it appends a non-standard octave number.
midi2note { arg midi;
var notes;
midi = (midi + 0.5).asInteger;
notes = ["c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"];
^(notes[midi%12] ++ (midi.div(12)-1))
}
// I found that the appended tab in str prevents end-clipping
// when the label is drawn, but it should not be needed.
formatLabel { | midi |
var str = this.midi2note(midi) ++ $\t;
^str.toUpper
}
getParams { |valueMin, valueMax, pixelMin, pixelMax, numTicks, tickSpacing = 64|
var lines, p;
var graphmin, graphmax, domain;
var offsetFromC = spec.default.asInteger;
var symScale = spec.units.asSymbol;
var theScale;
theScale = Scale.all[symScale];
graphmin = valueMin.round(1).asInteger;
graphmax = valueMax.round(1).asInteger;
lines = [];
domain = (graphmin..graphmax);
domain.do { | note, ix |
var deg = (note-offsetFromC).mod(theScale.pitchesPerOctave).round(1).asInteger;
if (theScale.degrees.includes(deg), {
lines = lines.add( note );
});
};
p = ();
p['lines'] = lines;
p['labels'] = lines.collect({ arg val; [val, this.formatLabel(val)] });
^p
}
}
ScaleWarp : LinearWarp {
gridClass { ^ScaleGridLines }
}
Now, make the Warp class aware of a new warp called ‘scale’, and add a Scale with an array of the degrees for which you want gridlines to be plotted. Grid.numTicks and Grid.tickSpacing will be ignored by ScaleGridLines.getParams(), since the number of semitones between scale degrees can be irregular. Instead, you will need to specify a suitably dense scale for your axis length.
Warp.warps.add(\scale -> ScaleWarp);
Scale.all.put(\semitones, Scale((0..11), name: "Semitones"));
Scale.all.put(\majorTriad, Scale(#[0, 4, 7], name: "Major Triad"));
Scale.all.put(\octaves, Scale(#[0], name: "Octaves"));
Now, it’s testing time! When creating the ControlSpec for an axis with the new warp ‘scale’,
the minval: and maxval: arguments must be MIDI note numbers. You specify the desired Scale with units: (its name can be a Symbol or a String, as you please), and the root note of the scale relative to C with default: (an Integer). Any of the predefined Scales with .pitchesPerOctave == 12 can be chosen.
x = ControlSpec.new(minval: 36, maxval: 72, warp: 'scale', default: 0, units: \majorTriad);
d = DrawGrid((600@250).asRect, x.grid, nil);
// generate a preview
~testView = d.preview;
Admittedly, this is unconventional usage of the ControlSpec, and it will only work with the custom warp \scale as implemented by the subclass ScaledGridLines shown above. Here is an example of plotting every semitone. Follow it with d = DrawGrid (…) and d.preview as before.
x = ControlSpec.new(48, 72, \scale, default: 0, units: \semitones);
Here’s the F minor scale, rather than the default C major. ‘minor’ is one of Scale’s predefined scales, and F is 5 semitones up from C. Again, follow it with d = DrawGrid (…) and d.preview as before.
x = ControlSpec.new(46, 80, \scale, default: 5, units: \minor);
And while we’re at it, has anyone made a class for drawing a piano keyboard along an axis?
Cheers,
sternsc