double evaluateFingerObstacleCost(FingerState state, float minPreparation, NoteChord obsNote, float startPosition, int startHeight, int pitch) { float deltaX = startPosition - Piano.KeyPositions[pitch]; float deltaY = startHeight - Piano.getKeyHeight(pitch); double distance = Math.Min(Math.Sqrt(deltaX * deltaX + deltaY * deltaY), 5) + 1; float prepareTime = startTime - minPreparation; float importance = NotationUtils.getNoteImportanceInChord(obsNote, state.Pitch); debug += string.Format("O{0},{1},{2},{3};", state.Pitch, prepareTime, state.Release, state.Press); if (prepareTime >= state.Release) { double coeff = CostCoeff.FINGER_MOVE_SPEED_PUNISH * Math.Pow(0.1, (startTime - state.Release) / minPreparation); return(coeff * distance); } else if (prepareTime >= state.Press) { double speedCost = CostCoeff.FINGER_MOVE_SPEED_PUNISH * distance; double cutoffCost = CostCoeff.NOTE_CUTOFF_PUNISH * importance * (state.Release - prepareTime) / (state.Release - state.Press); return(speedCost + cutoffCost); } else { return(CostCoeff.OMIT_KEY_PUNISH * importance); } }
void tryFingerChoice(int[] pitches, ref List <FingerChord> choices, FingerChord constrait, FingerChord fc, int currentNoteIndex, int emptyQuota) { if (currentNoteIndex >= pitches.Length) { choices.Add(new FingerChord(fc)); /*Finger[] fa = new Finger[fc.Values.Count]; * fc.Values.CopyTo(fa, 0); * UnityEngine.Debug.Log("fc: " + String.Join(",", Array.ConvertAll(fa, x => ((int)x).ToString())));*/ } else { int currentPitch = pitches[currentNoteIndex]; if (constrait != null && constrait.ContainsKey(currentPitch) && constrait[currentPitch] != Finger.EMPTY) { fc[currentPitch] = constrait[currentPitch]; tryFingerChoice(pitches, ref choices, constrait, fc, currentNoteIndex + 1, emptyQuota); } else { foreach (Finger f in FingerConstants.SolveTypeFingers[HandType]) { bool pass = true; for (int index = currentNoteIndex - 1; index >= 0; --index) { int pitch = pitches[index]; Finger finger = fc[pitch]; float distance = Piano.pitchPairDistance(pitch, currentPitch); pass = FingerConstants.testFingerDistance(finger, f, Config, distance); if (!pass) { break; } } if (pass) { fc[currentPitch] = f; tryFingerChoice(pitches, ref choices, constrait, fc, currentNoteIndex + 1, emptyQuota); } } if (emptyQuota > 0) { fc[currentPitch] = Finger.EMPTY; tryFingerChoice(pitches, ref choices, constrait, fc, currentNoteIndex + 1, emptyQuota - 1); } } } }
Choice evaluateChordChoice(FingerChord chord, int index) { float deltaTime = index > 0 ? NoteSeq[index].start - NoteSeq[index - 1].start : 0; NoteChord note = NoteSeq[index]; HandConfig.RangePair wrists = Config.getFingerChordWristRange(chord); double cost = 0; // wrist position naturality reward if (wrists.left != null) { float distance = Math.Abs(wrists.left.middle - -HandConfig.WristNaturePosition); cost = Math.Pow(distance / 14, 4) * CostCoeff.WRIST_POSITION_NATURALITY_REWARD; } if (wrists.right != null) { float distance = Math.Abs(wrists.right.middle - HandConfig.WristNaturePosition); cost = Math.Pow(distance / 14, 4) * CostCoeff.WRIST_POSITION_NATURALITY_REWARD; } // wrist crowd punish if (wrists.left != null && wrists.right != null) { float distance = Math.Max(Math.Abs(wrists.left.high - wrists.right.low), Math.Abs(wrists.right.high - wrists.left.low)); if (distance < 5) { cost += CostCoeff.WRIST_CROWD_PUNISH * (5f - distance) / 5f; } } foreach (Finger f in chord.Values) { // shift fingers punish if (Math.Abs((int)f) > 10) { cost += CostCoeff.SHIFT_FINGERS_PUNISH; } } int leftFingerCount = 0; int rightFingerCount = 0; foreach (var pair in chord) { if (pair.Value != Finger.EMPTY) { // black key short punish if (Piano.isBlackKey(pair.Key)) { int finger = Math.Abs((int)pair.Value); int first = (int)Math.Floor(finger / 10f) - 1; int second = finger % 10 - 1; float sh = HandConfig.BlackKeyShort[second]; if (first >= 0) { sh = Math.Max(HandConfig.BlackKeyShort[first], sh); } cost += sh * CostCoeff.BLACK_KEY_SHORT_PUNISH; } if (pair.Value > Finger.EMPTY) { ++rightFingerCount; } else if (pair.Value < Finger.EMPTY) { ++leftFingerCount; } } else { // omit key punish float importance = NotationUtils.getNoteImportanceInChord(note, pair.Key); cost += CostCoeff.OMIT_KEY_PUNISH * importance; } } // multiple fingers punish if (leftFingerCount > 0) { float value = leftFingerCount / 5f; cost += CostCoeff.MULTIPLE_FINGERS_PUNISH * value * value; } if (rightFingerCount > 0) { float value = rightFingerCount / 5f; cost += CostCoeff.MULTIPLE_FINGERS_PUNISH * value * value; } return(new Choice { chord = chord, staticCost = cost, wrists = wrists, deltaTime = deltaTime, node = new TreeNodeChoice() }); }
double evaluateSingleArmCost(HandConfig.Range lastWrist, HandConfig.Range currentWrist, FingerState[] lastFs, FingerState[] currentFs, int hand) { double cost = 0; // wrist offset punish // by middle cost += Math.Abs(currentWrist.middle - lastWrist.middle) * CostCoeff.WRIST_OFFSET_MIDDLE_PUNISH / timeUnit; debug += string.Format("FM: {0}\\n", Math.Abs(currentWrist.middle - lastWrist.middle) / timeUnit); // by range if (!(lastWrist.low < currentWrist.high && lastWrist.high > currentWrist.low)) { cost += Math.Min(Math.Abs(currentWrist.low - lastWrist.high), Math.Abs(lastWrist.low - currentWrist.high)) * CostCoeff.WRIST_OFFSET_RANGE_PUNISH / timeUnit; debug += string.Format("FR: {0}\\n", Math.Min(Math.Abs(currentWrist.low - lastWrist.high), Math.Abs(lastWrist.low - currentWrist.high)) / timeUnit); } // finger speed punish if (lastFs != null) { List <int> fingers = new List <int>(); List <int> pitches = new List <int>(); foreach (var pair in fingerChord) { int finger = (int)pair.Value * hand; if (finger > 0) { int first = (int)Math.Floor(finger / 10f) - 1; int second = finger % 10 - 1; if (first > 0) { fingers.Add(first); pitches.Add(pair.Key); } fingers.Add(second); pitches.Add(pair.Key); } } for (int i = 0; i < fingers.Count; ++i) { int finger = fingers[i]; int pitch = pitches[i]; //float position = Piano.KeyPositions[pitch]; FingerState state = lastFs[finger]; float minimumPreparationTime = s_BenchmarkDuration * 2 * HandConfig.MinimumPreparationRate; // self obstacle if (state.Index >= 0) { NoteChord obsNote = s_NoteSeq[state.Index]; cost += evaluateFingerObstacleCost(state, minimumPreparationTime * 2, obsNote, state.Position, state.Height, pitch); } // other obstacles if (finger > 0) { for (int of = 1; of < 5; ++of) { // ignore self finger if (of == finger) { continue; } FingerState obsState = lastFs[of]; if (obsState.Index >= 0) { int height = Piano.getKeyHeight(pitch); // allow ring finger on black cross pinky finger on white if (finger == 3 && of == 4 && height > obsState.Height) { continue; } float startPosition = obsState.Position + (finger - of) * hand; int startHeight = obsState.Height; float targetPosition = currentFs[finger].Position; if (state.Index >= 0) { startHeight = state.Height; if (finger * hand < of * hand) { startPosition = Math.Min(startPosition, state.Position); } else { startPosition = Math.Max(startPosition, state.Position); } } //debug += string.Format("of: {0}, {1}, {2}, {3}\\n", of, startPosition, obsState.Position, targetPosition); if ((targetPosition - obsState.Position) * (obsState.Position - startPosition) <= 0) { continue; } NoteChord obsNote = s_NoteSeq[obsState.Index]; cost += evaluateFingerObstacleCost(obsState, minimumPreparationTime, obsNote, startPosition, startHeight, pitch); //debug += "O" + ((of + 1) * hand).ToString() + ","/* + (startTime - minimumPreparationTime - obsState.Release).ToString() + ";"*/; } } } } } // TODO: return(cost); }
static FingerState[] generateFingerStates(FingerState[] parentStates, int hand, FingerChord chord, NoteChord nc, int index) { FingerState[] states = null; if (parentStates != null) { states = new FingerState[parentStates.Length]; Array.Copy(parentStates, states, states.Length); } foreach (var pair in chord) { int finger = (int)pair.Value * hand; if (finger > 0) { if (states == null) { states = Enumerable.Repeat(new FingerState { Press = -10000f, Release = -10000f, Index = -1, Height = 0 }, 5).ToArray(); for (int i = 0; i < states.Length; ++i) { states[i].Position = (HandConfig.WristNaturePosition + i - 2) * hand; } } Note note = nc.notes[pair.Key]; UnityEngine.Debug.Assert(note != null, "note and finger chord mismatch"); float position = Piano.KeyPositions[pair.Key]; int height = Piano.getKeyHeight(pair.Key); int first = (int)Math.Floor(finger / 10f) - 1; int second = finger % 10 - 1; if (first >= 0) { states[first].Press = note.start; states[first].Release = note.start; states[first].Pitch = pair.Key; states[first].Position = position; states[first].Height = height; states[first].Index = index; } UnityEngine.Debug.Assert(second >= 0 && second < 5, "invalid finger value"); { states[second].Press = note.start; states[second].Release = note.start + note.duration; states[second].Pitch = pair.Key; states[second].Position = position; states[second].Height = height; states[second].Index = index; //fixFingerStatesObstacle(ref states, hand, second); } } } return(states); }