public void GetRelativePitchDistanceTest() { // The distance must be computed on relative notes, i.e., the pitch must be taken modulo 12. Assert.AreEqual(0, MidiUtils.GetRelativePitchDistance(24, 48)); // Shortest distance via 7 to 8 = 2 Assert.AreEqual(2, MidiUtils.GetRelativePitchDistance(6, 8)); // Shortest distance via 1, 0, 11, 10 = 4 Assert.AreEqual(4, MidiUtils.GetRelativePitchDistance(2, 10)); // No distance Assert.AreEqual(0, MidiUtils.GetRelativePitchDistanceSigned(5, 5)); // Shortest signed distance from F to A -> 4 Assert.AreEqual(4, MidiUtils.GetRelativePitchDistanceSigned(53, 69)); // Shortest signed distance via 11, 0, 1, 2 -> 4 Assert.AreEqual(4, MidiUtils.GetRelativePitchDistanceSigned(10, 2)); // Shortest signed distance via 1, 0, 11, 10 -> -4 Assert.AreEqual(-4, MidiUtils.GetRelativePitchDistanceSigned(2, 10)); // Shortest signed distance via 5, 6, 7, 8 -> 4 Assert.AreEqual(4, MidiUtils.GetRelativePitchDistanceSigned(4, 8)); // Shortest signed distance via 7, 6, 5, 4 -> -4 Assert.AreEqual(-4, MidiUtils.GetRelativePitchDistanceSigned(8, 4)); // Shortest signed distance via 0 -> 1 Assert.AreEqual(1, MidiUtils.GetRelativePitchDistanceSigned(11, 0)); // Shortest signed distance via 11 -> -1 Assert.AreEqual(-1, MidiUtils.GetRelativePitchDistanceSigned(0, 11)); // Shortest signed distance from F to B -> 6 or -6 Assert.IsTrue(MidiUtils.GetRelativePitchDistanceSigned(77, 59) is - 6 or 6); // Shortest signed distance from A to D -> 5 Assert.AreEqual(5, MidiUtils.GetRelativePitchDistanceSigned(45, 74)); // Shortest signed distance from D to A -> -5 Assert.AreEqual(-5, MidiUtils.GetRelativePitchDistanceSigned(74, 45)); }
private void CreateUiRecordedNote(RecordedNote recordedNote, bool useRoundedMidiNote) { if (recordedNote.StartBeat == recordedNote.EndBeat) { return; } int midiNote = (useRoundedMidiNote) ? recordedNote.RoundedMidiNote : recordedNote.RecordedMidiNote; UiRecordedNote uiNote = Instantiate(uiRecordedNotePrefab, uiRecordedNotesContainer); if (micProfile != null) { uiNote.SetColorOfMicProfile(micProfile); } Text uiNoteText = uiNote.GetComponentInChildren <Text>(); if (showPitchOfNotes) { string pitchName = MidiUtils.GetAbsoluteName(midiNote); uiNoteText.text = " (" + pitchName + ")"; } else { uiNoteText.text = ""; } RectTransform uiNoteRectTransform = uiNote.GetComponent <RectTransform>(); PositionUiNote(uiNoteRectTransform, midiNote, recordedNote.StartBeat, recordedNote.EndBeat); recordedNoteToUiRecordedNoteMap[recordedNote] = uiNote; }
private void GenerateMasterBar(MasterBar masterBar, MasterBar previousMasterBar, int currentTick) { // time signature if (previousMasterBar == null || previousMasterBar.TimeSignatureDenominator != masterBar.TimeSignatureDenominator || previousMasterBar.TimeSignatureNumerator != masterBar.TimeSignatureNumerator) { _handler.AddTimeSignature(currentTick, masterBar.TimeSignatureNumerator, masterBar.TimeSignatureDenominator); } // tempo if (previousMasterBar == null) { _handler.AddTempo(currentTick, masterBar.Score.Tempo); _currentTempo = masterBar.Score.Tempo; } else if (masterBar.TempoAutomation != null) { _handler.AddTempo(currentTick, (int)masterBar.TempoAutomation.Value); _currentTempo = (int)(masterBar.TempoAutomation.Value); } // metronome if (GenerateMetronome) { var start = currentTick; var length = MidiUtils.ValueToTicks(masterBar.TimeSignatureDenominator); for (int i = 0; i < masterBar.TimeSignatureNumerator; i++) { _handler.AddMetronome(start, length); start += length; } } }
private void TestPitchDetection(Func <int, IAudioSamplesAnalyzer> audioSamplesAnalyzerProvider) { MicProfile micProfile = CreateDummyMicProfile(); string assetsPath = Application.dataPath; string sineWaveToneDir = assetsPath + "/Common/Audio/SineWaveTones/"; Dictionary <string, string> pathToExpectedMidiNoteNameMap = new(); pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a3-220hz.ogg", "A3"); pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a4-440hz.ogg", "A4"); pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a5-880hz.ogg", "A5"); pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c2-61,74hz.ogg", "C2"); pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c4-261,64hz.ogg", "C4"); pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c6-1046,50hz.ogg", "C6"); foreach (KeyValuePair <string, string> pathAndNoteName in pathToExpectedMidiNoteNameMap) { // Load the audio clip string uri = "file://" + pathAndNoteName.Key; AudioClip audioClip = AudioUtils.GetAudioClipUncached(uri, false); float[] samples = new float[audioClip.samples]; audioClip.GetData(samples, 0); // Analyze the samples IAudioSamplesAnalyzer audioSamplesAnalyzer = audioSamplesAnalyzerProvider(audioClip.frequency); PitchEvent pitchEvent = audioSamplesAnalyzer.ProcessAudioSamples(samples, 0, samples.Length - 1, micProfile); // Check result Assert.NotNull(pitchEvent, $"No pitch detected when analyzing audio resource {uri}"); string expectedName = pathAndNoteName.Value; string analyzedName = MidiUtils.GetAbsoluteName(pitchEvent.MidiNote); Assert.AreEqual(expectedName, analyzedName, $"Expected {expectedName} but was {analyzedName} when analyzing audio resource {uri}"); } }
private void HandlePitchEvent(PitchEvent pitchEvent, double currentBeat) { if (pitchEvent == null || pitchEvent.MidiNote <= 0) { if (lastRecordedNote != null) { HandleRecordedNoteEnded(currentBeat); } } else { if (lastRecordedNote != null) { if (MidiUtils.GetRelativePitchDistance(lastRecordedNote.RoundedMidiNote, pitchEvent.MidiNote) <= roundingDistance) { // Continue singing on same pitch HandleRecordedNoteContinued(currentBeat); } else { // Continue singing on different pitch. Finish the last recorded note. HandleRecordedNoteEnded(currentBeat); } } // The lastRecordedNote could be ended above, so the following null check is not redundant. if (lastRecordedNote == null && currentBeat >= nextNoteStartBeat) { // Start singing of a new note HandleRecordedNoteStarted(pitchEvent.MidiNote, currentBeat); } } }
private void OnPitchDetected(PitchEvent pitchEvent) { if (pitchEvent == null || !isCalibrationInProgress || pauseTime > 0) { return; } string targetMidiNoteName = midiNoteNames[currentIteration]; if (MidiUtils.GetAbsoluteName(pitchEvent.MidiNote) == targetMidiNoteName) { audioSource.Stop(); float delayInSeconds = Time.time - startTimeInSeconds; int delayInMillis = (int)(delayInSeconds * 1000); delaysInMillis.Add(delayInMillis); Debug.Log($"Mic delay calibration - delay of iteration {currentIteration}: {delayInMillis}"); currentIteration++; if (currentIteration >= midiNoteNames.Count) { OnEndCalibration(); } else { // Wait a bit for silence before the next iteration. pauseTime = 0.5f; } } else { Debug.Log("Mic delay calibration - wrong pitch: " + MidiUtils.GetAbsoluteName(pitchEvent.MidiNote)); } }
public DywaAudioSamplesAnalyzer(int sampleRateHz, int maxSampleLength) { this.maxSampleLength = maxSampleLength; halftoneFrequencies = MidiUtils.PrecalculateHalftoneFrequencies(MidiUtils.SingableNoteMin, MidiUtils.SingableNoteRange); // Create and configure Dynamic Wavelet Pitch Tracker. dywaPitchTracker = new DywaPitchTracker(); dywaPitchTracker.SampleRateHz = sampleRateHz; }
public void AddNote(int track, int start, int length, byte key, DynamicValue dynamicValue, byte channel) { var velocity = MidiUtils.DynamicToVelocity(dynamicValue); AddEvent(track, start, new MidiMessage(new[] { MakeCommand(0x90, channel), FixValue(key), FixValue((byte)velocity) })); AddEvent(track, start + length, new MidiMessage(new[] { MakeCommand(0x80, channel), FixValue(key), FixValue((byte)velocity) })); }
protected void CreateUiRecordedNote(RecordedNote recordedNote, bool useRoundedMidiNote) { if (recordedNote.StartBeat == recordedNote.EndBeat) { return; } // Pitch detection algorithms often have issues finding the correct octave. However, the octave is irrelevant for scores. // When notes are drawn far away from the target note because the pitch detection got the wrong octave then it looks wrong. // Thus, only the relative pitch of the roundedMidiNote is used and drawn on the octave of the target note. int midiNote; if (useRoundedMidiNote && recordedNote.TargetNote != null) { midiNote = MidiUtils.GetMidiNoteOnOctaveOfTargetMidiNote(recordedNote.RoundedMidiNote, recordedNote.TargetNote.MidiNote); } else { midiNote = recordedNote.RecordedMidiNote; } UiRecordedNote uiNote = Instantiate(uiRecordedNotePrefab, uiRecordedNotesContainer); uiNote.RecordedNote = recordedNote; uiNote.StartBeat = recordedNote.StartBeat; uiNote.TargetEndBeat = recordedNote.EndBeat; // Draw already a portion of the note uiNote.LifeTimeInSeconds = Time.deltaTime; uiNote.EndBeat = recordedNote.StartBeat + (uiNote.LifeTimeInSeconds * beatsPerSecond); uiNote.MidiNote = midiNote; if (micProfile != null) { uiNote.SetColorOfMicProfile(micProfile); } Text uiNoteText = uiNote.lyricsUiText; if (showPitchOfNotes) { string pitchName = MidiUtils.GetAbsoluteName(midiNote); uiNoteText.text = " (" + pitchName + ")"; } else { uiNoteText.text = ""; } RectTransform uiNoteRectTransform = uiNote.RectTransform; PositionUiNote(uiNoteRectTransform, midiNote, uiNote.StartBeat, uiNote.EndBeat); uiRecordedNotes.Add(uiNote); recordedNoteToUiRecordedNotesMap.AddInsideList(recordedNote, uiNote); }
public CamdAudioSamplesAnalyzer(int sampleRateHz, int maxSampleLength) { this.maxSampleLength = maxSampleLength; float[] halftoneFrequencies = MidiUtils.PrecalculateHalftoneFrequencies(MinNote, NumHalftones); halftoneDelays = MidiUtils.PrecalculateHalftoneDelays(sampleRateHz, halftoneFrequencies); for (int i = 0; i < currentCandidates.Length; i++) { currentCandidates[i] = new CamdPitchCandidate(); } }
private void OnPitchDetected(int midiNote) { // Show the note that has been detected if (midiNote > 0) { currentNoteLabel.text = "Note: " + MidiUtils.GetAbsoluteName(midiNote); } else { currentNoteLabel.text = "Note: unkown"; } }
/// <inheritdoc /> public void AddNote(int track, int start, int length, byte key, DynamicValue dynamicValue, byte channel) { var velocity = MidiUtils.DynamicToVelocity(dynamicValue); var noteOn = new MidiEvent(start, MakeCommand((byte)MidiEventType.NoteOn, channel), FixValue(key), FixValue((byte)velocity)); _midiFile.AddEvent(noteOn); var noteOff = new MidiEvent(start + length, MakeCommand((byte)MidiEventType.NoteOff, channel), FixValue(key), FixValue((byte)velocity)); _midiFile.AddEvent(noteOff); }
private void OnPitchDetected(PitchEvent pitchEvent) { // Show the note that has been detected if (pitchEvent != null && pitchEvent.MidiNote > 0) { currentNoteLabel.text = "Note: " + MidiUtils.GetAbsoluteName(pitchEvent.MidiNote); } else { currentNoteLabel.text = "Note: ?"; } }
private void UpdateLastMidiNoteFields(PitchEvent pitchEvent) { if (pitchEvent == null) { lastMidiNoteName = ""; lastMidiNote = 0; } else { lastMidiNoteName = MidiUtils.GetAbsoluteName(pitchEvent.MidiNote); lastMidiNote = pitchEvent.MidiNote; } }
public void TestMethod() { for (int i = 0; i < 128; i++) { float freq = MidiUtils.MidiNoteToPitch(i); int note = 0; int cents = 0; MidiUtils.PitchToMidiNote(freq, out note, out cents); string noteName = MidiUtils.GetNoteName(note, false, true); Console.Out.WriteLine("Midi Key: {0}, Frequency: {1:0.0000} (note: {2} {3} cents - {4})", i, freq, note, cents, noteName); } }
/// <summary> /// Encodes a Note On short message. /// </summary> /// <param name="channel">MIDI channel.</param> /// <param name="noteId">Note ID.</param> /// <param name="velocity">Note velocity.</param> /// <returns>A value that can be passed to midiOutShortMsg.</returns> /// <exception cref="ArgumentOutOfRangeException">noteId is not in MIDI range.</exception> public static uint EncodeNoteOn(Channel channel, int noteId, int velocity) { channel.Validate(); if (!MidiUtils.IsInMidiRange(noteId)) { throw new ArgumentOutOfRangeException("Pitch out of MIDI range."); } if (velocity < 0 || velocity > 127) { throw new ArgumentOutOfRangeException("Velocity is out of range."); } return((uint)(0x90 | ((int)channel) | (noteId << 8) | (velocity << 16))); }
private int GetRoundedMidiNote(int recordedMidiNote, int targetMidiNote, int roundingDistance) { int distance = MidiUtils.GetRelativePitchDistance(recordedMidiNote, targetMidiNote); if (distance <= roundingDistance) { return(targetMidiNote); } else { return(recordedMidiNote); } }
public void AddMacro(string note, Macro macro) { if (!MidiUtils.IsNoteNameValid(note)) { throw new ArgumentException($"'{note}' is not a valid note name"); } if (_macros.Contains(note)) { throw new ArgumentException($"Macro in note '{note}' already exists"); } _macros.Add(note, macro); }
public void SyncWithNote() { bool isSelected = selectionController.IsSelected(Note); goldenNoteImageOverlay.gameObject.SetActive(Note.IsGolden); SetLyrics(Note.Text); SetSelected(isSelected); pitchLabel.text = MidiUtils.GetAbsoluteName(Note.MidiNote); if (Note.Sentence != null && Note.Sentence.Voice != null) { Color color = songEditorSceneController.GetColorForVoice(Note.Sentence.Voice); SetColor(color); } }
private void UpdatePosition(VirtualPianoKeyControl keyControl) { float heightPercent = noteAreaControl.HeightForSingleNote * 0.8f; float yPercent = (float)noteAreaControl.GetVerticalPositionForMidiNote(keyControl.MidiNote) - heightPercent / 2; float widthPercent = MidiUtils.IsWhitePianoKey(keyControl.MidiNote) ? 0.9f : 0.7f; VisualElement visualElement = keyControl.VisualElement; visualElement.style.position = new StyleEnum <Position>(Position.Absolute); visualElement.style.top = new StyleLength(new Length(yPercent * 100, LengthUnit.Percent)); visualElement.style.height = new StyleLength(new Length(heightPercent * 100, LengthUnit.Percent)); visualElement.style.left = 0; visualElement.style.width = new StyleLength(new Length(widthPercent * 100, LengthUnit.Percent)); }
public override void RunSuite() { ///// Export midi. StepCollection steps = new StepCollection(); Dictionary <int, string> channels = new Dictionary <int, string>(); new List <int>() { 1, 2, 3 }.ForEach(i => channels.Add(i, $"CHANNEL_{i}")); MidiUtils.ExportMidi(steps, "midiFileName", channels, 1.0, "string info"); }
private static Macro ParseMacro(string serializedMacro, int lineNumber, out string note) { string[] parameters = serializedMacro.Split(Macro.SerializeDelimiter); if (parameters.Length != Macro.SerializeParamsCount) { throw new ParseProfileFileException(GetErrorMessage($"Macro needs to have exactly {Macro.SerializeParamsCount} parameters", lineNumber)); } // Parse and validate macro name string macroName = parameters[Macro.SerializeNameIndex]; if (string.IsNullOrWhiteSpace(macroName) || macroName.Length > Macro.MaxMacroNameSize) { throw new ParseProfileFileException(GetErrorMessage( $"Macro name size needs to be between 1-{Macro.MaxMacroNameSize} characters", lineNumber, Macro.SerializeNameIndex + 1) ); } // Parse and validate note name note = parameters[Macro.SerializeNoteIndex]; if (!MidiUtils.IsNoteNameValid(note)) { throw new ParseProfileFileException(GetErrorMessage("Invalid note name", lineNumber, Macro.SerializeNoteIndex + 1)); } // Parse and validate macro type string typeName = parameters[Macro.SerializeTypeIndex].Replace("_", ""); if (!Enum.TryParse(typeName, true, out Macro.MacroType type)) { throw new ParseProfileFileException(GetErrorMessage("Invalid macro type", lineNumber, Macro.SerializeTypeIndex + 1)); } // Parse and validate macro options string macroOptions = parameters[Macro.SerializeOptionsIndex]; if (string.IsNullOrWhiteSpace(macroOptions)) { throw new ParseProfileFileException(GetErrorMessage("Macro options must not be empty", lineNumber, Macro.SerializeOptionsIndex + 1)); } try { // Deserialize macro return(Macro.DeserializeMacro(macroName, note, type, macroOptions)); } catch (DeserializeMacroException ex) { throw new ParseProfileFileException(GetErrorMessage(ex.Message, lineNumber, Macro.SerializeOptionsIndex + 1)); } }
private void OnPitchDetected(PitchEvent pitchEvent) { // Show the note that has been detected if (pitchEvent != null && pitchEvent.MidiNote > 0) { currentNoteLabel.text = TranslationManager.GetTranslation(R.Messages.options_note, "value", MidiUtils.GetAbsoluteName(pitchEvent.MidiNote)); } else { currentNoteLabel.text = TranslationManager.GetTranslation(R.Messages.options_note, "value", "?"); } }
private void OnBeatAnalyzed(PlayerPitchTracker.BeatAnalyzedEvent beatAnalyzedEvent) { // Check if pitch was detected where a note is expected in the song if (beatAnalyzedEvent.PitchEvent == null || beatAnalyzedEvent.NoteAtBeat == null) { return; } if (beatAnalyzedEvent.Beat < NextBeatToScore) { return; } Note analyzedNote = beatAnalyzedEvent.NoteAtBeat; // Check if note was hit if (MidiUtils.GetRelativePitch(beatAnalyzedEvent.RoundedRecordedMidiNote) != MidiUtils.GetRelativePitch(analyzedNote.MidiNote)) { return; } // The beat was sung correctly. if (!ScoreData.NoteToNoteScoreMap.TryGetValue(analyzedNote, out NoteScore noteScore)) { noteScore = new NoteScore(analyzedNote); ScoreData.NoteToNoteScoreMap.Add(analyzedNote, noteScore); } noteScore.CorrectlySungBeats++; Sentence analyzedSentence = beatAnalyzedEvent.NoteAtBeat.Sentence; if (!ScoreData.SentenceToSentenceScoreMap.TryGetValue(analyzedSentence, out SentenceScore sentenceScore)) { sentenceScore = CreateSentenceScore(analyzedSentence); ScoreData.SentenceToSentenceScoreMap.Add(analyzedSentence, sentenceScore); } if (IsPerfectHit(beatAnalyzedEvent)) { ScoreData.GetBeatData(analyzedNote).IfNotNull(it => it.PerfectBeats++); sentenceScore.GetBeatData(analyzedNote).IfNotNull(it => it.PerfectBeats++); } else if (IsGoodHit(beatAnalyzedEvent)) { ScoreData.GetBeatData(analyzedNote).IfNotNull(it => it.GoodBeats++); sentenceScore.GetBeatData(analyzedNote).IfNotNull(it => it.GoodBeats++); } }
private void OnBeatAnalyzed(PlayerPitchTracker.BeatAnalyzedEvent beatAnalyzedEvent) { // Check if pitch was detected where a note is expected in the song if (beatAnalyzedEvent.PitchEvent == null || beatAnalyzedEvent.NoteAtBeat == null) { return; } if (beatAnalyzedEvent.Beat < NextBeatToScore) { return; } Note analyzedNote = beatAnalyzedEvent.NoteAtBeat; // Check if note was hit if (MidiUtils.GetRelativePitch(beatAnalyzedEvent.RoundedMidiNote) != MidiUtils.GetRelativePitch(analyzedNote.MidiNote)) { return; } // The beat was sung correctly. if (!ScoreData.NoteToNoteScoreMap.TryGetValue(analyzedNote, out NoteScore noteScore)) { noteScore = new NoteScore(analyzedNote); ScoreData.NoteToNoteScoreMap.Add(analyzedNote, noteScore); } noteScore.CorrectlySungBeats++; Sentence analyzedSentence = beatAnalyzedEvent.NoteAtBeat.Sentence; if (!ScoreData.SentenceToSentenceScoreMap.TryGetValue(analyzedSentence, out SentenceScore sentenceScore)) { sentenceScore = new SentenceScore(analyzedSentence); ScoreData.SentenceToSentenceScoreMap.Add(analyzedSentence, sentenceScore); } if (analyzedNote.IsNormal) { ScoreData.CorrectNormalNoteLengthTotal++; sentenceScore.CorrectlySungNormalBeats++; } else if (analyzedNote.IsGolden) { ScoreData.CorrectGoldenNoteLengthTotal++; sentenceScore.CorrectlySungGoldenBeats++; } }
protected Macro(string macroName, string note) { if (!MidiUtils.IsNoteNameValid(note)) { throw new ArgumentException($"'{note}' is not a valid note name"); } if (macroName.Length > MaxMacroNameSize) { throw new ArgumentException($"Macro must not exceed {MaxMacroNameSize} characters"); } MacroName = macroName; Note = note; }
/// <summary> /// Constructs a note message. /// </summary> public NoteMessage(Channel channel, bool noteOn, int noteId, int velocity) : base(channel) { if (!MidiUtils.IsInMidiRange(noteId)) { throw new ArgumentOutOfRangeException("note ID is out of MIDI range."); } if (velocity < 0 || velocity > 127) { throw new ArgumentOutOfRangeException("velocity"); } this.NoteOn = noteOn; this.NoteId = noteId; this.Velocity = velocity; }
private int GetCorrectlySungNoteLength(RecordedNote recordedNote) { if (recordedNote.TargetNote == null) { return(0); } if (MidiUtils.GetRelativePitch(recordedNote.TargetNote.MidiNote) != MidiUtils.GetRelativePitch(recordedNote.RoundedMidiNote)) { return(0); } int correctlySungNoteLength = (int)(recordedNote.EndBeat - recordedNote.StartBeat); return(correctlySungNoteLength); }
public void GetAbsoluteNameTest() { Dictionary <int, string> midiNoteToAbsoluteNameMap = new(); midiNoteToAbsoluteNameMap.Add(36, "C2"); midiNoteToAbsoluteNameMap.Add(57, "A3"); midiNoteToAbsoluteNameMap.Add(60, "C4"); midiNoteToAbsoluteNameMap.Add(69, "A4"); midiNoteToAbsoluteNameMap.Add(81, "A5"); midiNoteToAbsoluteNameMap.Add(84, "C6"); foreach (KeyValuePair <int, string> midiNoteAndName in midiNoteToAbsoluteNameMap) { string noteName = MidiUtils.GetAbsoluteName(midiNoteAndName.Key); Assert.AreEqual(midiNoteAndName.Value, noteName); } }
public void GetRelativeNameTest() { Dictionary <int, string> midiNoteToRelativeNameMap = new Dictionary <int, string>(); midiNoteToRelativeNameMap.Add(36, "C"); midiNoteToRelativeNameMap.Add(57, "A"); midiNoteToRelativeNameMap.Add(60, "C"); midiNoteToRelativeNameMap.Add(69, "A"); midiNoteToRelativeNameMap.Add(81, "A"); midiNoteToRelativeNameMap.Add(84, "C"); foreach (KeyValuePair <int, string> midiNoteAndName in midiNoteToRelativeNameMap) { string noteName = MidiUtils.GetRelativeName(midiNoteAndName.Key); Assert.AreEqual(midiNoteAndName.Value, noteName); } }