Code/Engine/Voices/Keys.cs

Part of the MusicGen class that renders keyboard/keys parts for different genres. It defines per-genre timbre helpers, an EmitKeys patch builder, and four rendering routines: rock stab, country honky-tonk, pop pad, and a pop sixteenth arp; each iterates hits, computes chord tones and calls RenderPatch.

Native Interop
using System;
using System.Collections.Generic;

using static Skafinity.Osc;

namespace Skafinity;

// The keyboard voices: rock's syncopated organ comp, country's honky-tonk piano, pop's pad and
// its sixteenth arpeggio. One voice, four grammars — the figure comes from the genre's table.
//
// Part of the MusicGen engine — see MusicGen.cs.

public sealed partial class MusicGen
{
	// The keys' timbre, per genre — next to the voice, because it is the sound of an instrument
	// rather than the identity of a genre. Rock drives its organ dirty; country's piano and pop's
	// synth stay clean and bright.
	float KeysDriveFor() => _genre == 2 || _genre == 5
		? 1f + 0.2f * MathF.Max( 1f, _c.KeysDrive )
		: MathF.Max( 1f, _c.KeysDrive );

	/// <summary>Per-genre level (measured with `--levels`). Pop's pad is a held chord and pours
	/// far more energy into the mix than country's stabs at the same balance.</summary>
	float KeysLevel() => _genre == 5 ? 0.75f : 1f;

	void EmitKeys( int tick, int durTicks, int midi, float vel, bool ring, int voices, Voicing vc )
	{
		int dur = _time.SpanSamples( tick, durTicks );
		double dec = _time.SpanSeconds( tick, durTicks ) * (ring ? 0.9 : 0.4);
		var keys = new Patch
		{
			Osc = 1, Voices = 2, Detune = _c.Detune * 0.5f,
			Amp = _c.KeysVol * _c.KeysBalance * KeysLevel() * _midMul / Math.Max( 1, voices ) * NoteGain( vel ),
			Attack = 0.004f, Decay = dec, Sustain = ring ? 0.6f : 0.2f, Sustained = ring,
			Cutoff = _c.KeysCutoff, CutEnv = 250f, Reso = 1.0f,
			Drive = KeysDriveFor(), Pan = 0f,
		};
		ApplyVoicing( ref keys, vc );
		// NOT double-tracked. The keys already sound their whole voicing as simultaneous notes, each
		// a 2-voice detuned unison; doubling that put four detuned oscillators on every chord tone
		// and the beating between them read as an out-of-tune keyboard rather than as width. A
		// doubled part wants to be one line or a strum, not a stack of held thirds.
		RenderPatch( _time.TickToSample( tick ), dur, Midi( midi ), keys, mono: true );
	}

	// ── Rock keys: the Charleston stab ──
	// Its own syncopation against the guitar's riff, an octave above it, playing the full voicing
	// where the guitar plays a power chord — different notes AND a different rhythm, so the two
	// interlock instead of doubling.
	void RenderKeysStabBar( List<Hit> hits, int chord, Rng rng, Rng exprRng )
	{
		int kBase = Register( 2 );
		float chug = Math.Clamp( _c.KeysChug, 0f, 1f );
		bool ring = chug < 0.5f;
		var vc = Roll( Expr( "KEYS" ), 0, NoPrev, exprRng );
		foreach ( var h in hits )
		{
			int len = Math.Max( 1, (int)(CompLen( h.SpanTicks, ring ) * Math.Max( 0.25f, 1f - 0.7f * chug )) );
			foreach ( var (t, d) in ChordSegments( h.Tick, len ) )
			{
				var tones = ChordMidis( kBase, chord, t );
				foreach ( var m in tones )
					EmitKeys( t, d, m, h.Vel, ring, tones.Length, vc );
			}
		}
	}

	// ── Country keys: honky-tonk piano ──
	// It answers the backbeat rather than keeping time — short two-handed stabs on 2 and 4, with
	// the root doubled an octave down the way a piano player's left hand does.
	void RenderHonkyTonkBar( List<Hit> hits, int chord, Rng rng, Rng exprRng )
	{
		int kBase = Register( 2 );
		var vc = Roll( Expr( "KEYS" ), 0, NoPrev, exprRng );
		foreach ( var h in hits )
		{
			int len = Math.Max( 1, (int)(CompLen( h.SpanTicks, false ) * 0.9f) );
			foreach ( var (t, d) in ChordSegments( h.Tick, len ) )
			{
				var tones = ChordMidis( kBase, chord, t );
				foreach ( var m in tones )
					EmitKeys( t, d, m, h.Vel, false, tones.Length + 1, vc );
				EmitKeys( t, d, tones[0] - 12, h.Vel * 0.8f, false,
					tones.Length + 1, vc );
			}
		}
	}

	// ── Pop: the pad ──
	// One hit a bar, held. The harmony is a bed here rather than a rhythm part, which is exactly
	// what lets the arp on top be the moving voice.
	void RenderPadBar( List<Hit> hits, int chord, Rng rng, Rng exprRng )
	{
		int kBase = Register( 2 );
		float pluck = Math.Clamp( _c.KeysChug, 0f, 1f );
		var vc = Roll( Expr( "KEYS" ), 0, NoPrev, exprRng );
		foreach ( var h in hits )
		{
			int len = Math.Max( 1, (int)(h.SpanTicks * Math.Max( 0.2f, 1f - 0.75f * pluck )) );
			foreach ( var (t, d) in ChordSegments( h.Tick, len ) )
			{
				var tones = ChordMidis( kBase, chord, t );
				foreach ( var m in tones )
					EmitKeys( t, d, m, h.Vel * 0.85f * _compTrim, pluck < 0.5f,
						tones.Length, vc );
			}
		}
	}

	// ── Pop: the sixteenth arp ──
	// Single tones climbing the voicing, so an add9 arp reaches the 9th without the figure
	// knowing what a 9th is (the cell names a voice index; the song's voicing says what that is).
	void RenderArpBar( List<Hit> hits, int chord, Rng rng, Rng exprRng )
	{
		int kBase = Register( 2 );
		var vc = Roll( Expr( "KEYS" ), 0, NoPrev, exprRng );
		foreach ( var h in hits )
		{
			if ( !CompFigure.IsTone( h.Value ) ) continue;
			int len = Math.Max( 1, (int)(h.SpanTicks * 0.9f) );
			EmitKeys( h.Tick, len,
				ChordToneMidi( kBase, chord, CompFigure.ToneIndex( h.Value ), h.Tick ),
				h.Vel * 0.7f, false, 1, vc );
		}
	}
}