// Returned list contains one value for each note, computed with simple single-level model public List <float> GetNoteExpectednessLarson(Key key, Alphabet alphabet) { List <NoteWithAttackPoint> notes = this.AllNotes; List <float> noteExpectednessLarson = new List <float>(notes.Count); Note n1 = null; Note n2 = null; for (int i = 0; i < notes.Count; i++) { Note n3 = notes[i]; int G = 0, I = 0; float M = 0; // Require at least 2 notes for Gravity and Magnetism forces, all 3 required for Inertia. if (n2 != null) { int n2MIDI = n2.Midi; ScaleDegree sd2 = n2.GetScaleDegree(key); if (sd2 != null) { int diff2 = n2MIDI - n3.Midi; // Gravity. if (diff2 > 0 && (sd2 == null || !sd2.IsTonic)) { G = 1; } // Magnetism. // Is previous note a stable note? If so, no prediction. if (!alphabet.isStable(sd2)) { // Not stable. Compute the magnetism based on the distance in half-steps to the nearest goals from the 2nd pitch. // Go up until we find a stable note. int stableAboveMidi = -1; int stableBelowMidi = -1; for (int midi = n2MIDI + 1; ; midi++) { ScaleDegree sdx = new Note(-1, false, false, new MidiInfo(midi, Accidental.Natural)).GetScaleDegree(key); if (sdx != null) { if (alphabet.isStable(sdx)) { stableAboveMidi = midi; break; } } } for (int midi = n2MIDI - 1; ; midi--) { ScaleDegree sdx = new Note(-1, false, false, new MidiInfo(midi, Accidental.Natural)).GetScaleDegree(key); if (sdx != null) { if (alphabet.isStable(sdx)) { stableBelowMidi = midi; break; } } } int distAbove = stableAboveMidi - n2MIDI; int distBelow = n2MIDI - stableBelowMidi; float mag = 1.0f / (distAbove * distAbove) - 1.0f / (distBelow * distBelow); float magMag = Math.Abs(mag); // magnitude of magnetism // Test direction if (diff2 * mag > 0) { // Going in opposite direction (because diff2 is positive when going down and mag is positive when going up. M = -magMag; } else { // Going in same direction. M = magMag; } } // Inertia. if (n1 != null) { int diff1 = n1.Midi - n2.Midi; if (diff1 != 0) { I = (diff1 * diff2 > 0) ? 1 : -1; } } } } /// Compute force of expecting this note. float force = G * Constants.LARSON_WEIGHT_G + I * Constants.LARSON_WEIGHT_I + M * Constants.LARSON_WEIGHT_M; noteExpectednessLarson.Add(force); //throw new Exception("Force computed: " + force.ToString()); n1 = n2; n2 = n3; } return(noteExpectednessLarson); }
public override void Run() { if (group == null) { group = workspace.PickRandomGroupByRecency(); } if (group == null) { return; } if (!workspace.groups.Contains(group)) { return; } // Add to attention history. workspace.RecordCodeletAttentionHistory(this, group.MinLocation); workspace.RecordCodeletAttentionHistory(this, group.MaxLocation); // Check the final note of the rhythm. // Score based on the duration and tonic/dominantness of the pitch. Measure m = group.Measures[group.Measures.Count - 1]; Note n = m.rhythm.notes[m.rhythm.notes.Count - 1]; if (n.midiInfo == null) { return; } ScaleDegree degree = n.midiInfo.GetScaleDegree(new Key()); // assume C major. int dur = n.duartionIncludingTiesAfter; double score = 0; // check for minor/chromatic. if (degree == null) { // maybe its minor (TODO: crude) degree = n.midiInfo.GetScaleDegree(new Key()); // assume C minor. } // maybe it's chromatic if (degree == null) { return; } else { switch (degree.Number) { case 1: // tonic score = 100; break; case 5: // dominant score = 100; break; case 2: // supertonic score = 30; break; default: return; } } // Now average in harmonic context of pitch, if available. double str1 = group.AlphabetStrength; double str2 = m.AlphabetStrength; Alphabet alphabet = null; if (str1 >= str2) { alphabet = group.Alphabet; } else { alphabet = m.Alphabet; } if (alphabet != null) { // Average in 0 or 100 points depending on stability if (alphabet.isStable(degree)) { score = (score + 100) / 2; } else { score = (score + 0) / 2; } } // Duration if (dur < 8) { score = score * (dur / 8.0); } if (score > 25) { if (degree.Number == 1) { group.AddGroupReason(new GroupReasonEndTonic(group, score)); } else { group.AddGroupReason(new GroupReasonEndDominant(group, score)); } } }