/*************************************************************************//** * @} * @defgroup SongPubFunc Public Functions * @ingroup DocSong * Functions that allow other classes to interact with the Song. * @{ *****************************************************************************/ /** * @brief Adds a @link Music::CombinedNote note@endlink to the Song. * @param[in] aNewNote The @link Music::CombinedNote note@endlink to add. */ public void AddNote(Music.CombinedNote aNewNote) { // Update the number of musical notes in the Song if needed. if (aNewNote.NumPitches > 0) { mNumMusicalNotes++; for (int i = 0; i < aNewNote.NumPitches; i++) { // Update the highest pitch. if (aNewNote.MusicalNote.Pitches[i] != Music.PITCH.REST && mHighestPitch < aNewNote.MusicalNote.Pitches[i]) { mHighestPitch = aNewNote.MusicalNote.Pitches[i]; } // Update the lowest pitch. if (aNewNote.MusicalNote.Pitches[i] != Music.PITCH.REST && mLowestPitch > aNewNote.MusicalNote.Pitches[i]) { mLowestPitch = aNewNote.MusicalNote.Pitches[i]; } } } // Update the number of notes with drum hits in the Song if needed. if (aNewNote.NumDrums > 0) { mNumDrumNotes++; } // Add the note to the list. mNotes.Add(aNewNote); // Update the Song type. UpdateSongType(); }
/** * @brief Parses the @link Music::PITCH pitches@endlink from a string in a @link DocSongFileFormat Song file@endlink. * @param[in] aStringFromFile The string to parse. * @see @link DocSongFileFormat Song File Format@endlink */ private Music.PITCH[] ParsePitches(string aStringFromFile) { Music.PITCH[] parsedPitches = null; // Get the entire string of pitches. string pitchString = aStringFromFile; if (mType == SongType.CombinedMelodyAndPercussion) { pitchString = aStringFromFile.Split('|')[0]; } // If there are pitches for the note, then parse each pitch. if (pitchString != "null") { // Split the string into individual pitches. string[] pitches = pitchString.Split(','); // Iterate through each pitch and parse it. parsedPitches = new Music.PITCH[pitches.Length]; for (int i = 0; i < pitches.Length; i++) { parsedPitches[i] = (Music.PITCH) int.Parse(pitches[i]); } } // Return the parsed pitches. return(parsedPitches); }
private Toggle[] mPitches = null; //!< The toggle switches for each @link Music::PITCH pitch@endlink/@link Music::DRUM drum@endlink. /*************************************************************************//** * @} * @defgroup SC_PSCPubFunc Public Functions * @ingroup DocSC_PSC * Functions for other classes to interact with the SC_PitchSelectionContainer * @{ *****************************************************************************/ /** * @brief Gets the currently selected @link Music::PITCH pitches@endlink/@link Music::DRUM drums@endlink. * @return The currently selected @link Music::PITCH pitches@endlink/@link Music::DRUM drums@endlink. */ public Music.PITCH[] GetSelectedPitches() { Music.PITCH[] returned = null; // If we're currently a rest note, return the rest pitch if (mRestNote) { returned = new Music.PITCH[1]; returned[0] = Music.PITCH.REST; } // If there aren't any pitches selected, then return null. else if (mSelectedPitches.Count == 0) { return(null); } // If we're not currently a rest note and some pitches are selected, then return all of the selected pitches. else { int index = 0; returned = new Music.PITCH[mSelectedPitches.Count]; foreach (int pitch in mSelectedPitches) { returned[index] = (Music.PITCH)mSelectedPitches[index]; index++; } } return(returned); }
/*************************************************************************//** * @} * @defgroup VIBaseProFunc Protected Functions * @ingroup VIBase * Functions that are used by the subclasses. * @{ ****************************************************************************/ /** * @brief Loads all the audio clips associated with a pitch. * @param[in] aPitch The pitch to load the audio clips for. */ protected void LoadAudioClipForPitch(Music.PITCH aPitch) { Assert.IsTrue(aPitch >= mLowestSupportedPitch && aPitch <= mHighestSupportedPitch, "Tried to load the pitch " + Music.NoteToString(aPitch) + ", but that's out of the instrument's range from " + Music.NoteToString(mLowestSupportedPitch) + " to " + Music.NoteToString(mHighestSupportedPitch)); // Get the file index. int fileIndex = (int)aPitch - (int)mLowestSupportedPitch; // Get the audio clips for the pitch. AudioClip[] clips = null; // If Built-In Dynamics are not supported, then just get the one clip. if (mNumBuiltInDynamics == 0) { // Get the audio file. clips = new AudioClip[1]; clips[0] = Resources.Load <AudioClip>(mFilenames[fileIndex]); Assert.IsNotNull(clips[0], "Failed to load audioclip from file " + mFilenames[fileIndex]); // Load the audio data. clips[0].LoadAudioData(); // Normalize the clip. NormalizeAudioClipsForPitch(aPitch, clips); } // If Built-In Dynamics are supported, then get all of the associated clips. else { // Allocate space for the clips. clips = new AudioClip[mNumBuiltInDynamics]; // Load the clips. for (int i = 0; i < mNumBuiltInDynamics; i++) { // Load the wav file. clips[i] = Resources.Load <AudioClip>(mFilenames[fileIndex]); Assert.IsNotNull(clips[0], "Failed to load audioclip from file " + mFilenames[fileIndex]); // Load the audio data into the AudioClip. clips[i].LoadAudioData(); // Go to the next file. fileIndex += mNumSupportedPitches; } // Normalize the clips. NormalizeAudioClipsForPitch(aPitch, clips); } // Clean up. clips = null; Resources.UnloadUnusedAssets(); }
/** * @brief Handler for when a song begins playing. * @param[in] aSong The song that began playing. */ private void HandlePlaySongEvent(Song aSong) { // Keep track of the current key layout. Music.PITCH[] currentLayout = mRepresentedPitches; // Get the highest note in the song. Music.PITCH highestPitch = aSong.GetHighestPitch(); // Get the lowest note in the song. Music.PITCH lowestPitch = aSong.GetLowestPitch(); // Get the song data. Song.CombinedNoteData[] noteData = aSong.GetNoteData(); // Clear the keyboard. ClearKeyboard(); // Set the range of keys. mRepresentedPitches = null; int numPitches = (int)highestPitch - (int)lowestPitch + 1; mRepresentedPitches = new Music.PITCH[numPitches]; mNumWhiteKeys = 0; for (int i = 0; i < numPitches; i++) { mRepresentedPitches[i] = (Music.PITCH)((int)lowestPitch + i); if (!Music.IsPitchABlackKey(mRepresentedPitches[i])) { mNumWhiteKeys++; } } // Load the proper keys. LoadKeys(); // Get the sample interval (seconds between waveform samples.) float sampInt = VirtualInstrument.SAMPLE_INTERVAL; // Draw the lesson markers for each pitch. foreach (Song.CombinedNoteData note in noteData) { if (note.MelodyData.Pitches != null) { int index = 0; foreach (Music.PITCH pitch in note.MelodyData.Pitches) { StartCoroutine(DrawLessonMarker((int)pitch - (int)lowestPitch, (float)note.TotalOffset * sampInt, (note.MelodyData.NumSamples[index] * sampInt) - .02f)); index++; } } } }
/** * @brief Replaces a note at a given index. * @param[in] aNote The note that will be put into the Song at the given index. * @param[in] aIndex The index of the place to insert the note. */ public void ReplaceNote(Music.CombinedNote aNote, int aIndex) { Assert.IsTrue(aIndex < mNotes.Count, "Tried to replace a note at the index " + aIndex.ToString() + " for a Song with only " + mNotes.Count.ToString() + " notes in it!"); // Get the replaced note. Music.CombinedNote replacedNote = mNotes[aIndex]; // Replace the note. mNotes[aIndex] = aNote; // Account for changes in the number of musical notes and drum notes in the Song. if (replacedNote.NumPitches > 0 && aNote.NumPitches == 0) { mNumMusicalNotes--; } else if (replacedNote.NumPitches == 0 && aNote.NumPitches > 0) { mNumMusicalNotes++; } if (replacedNote.NumDrums > 0 && aNote.NumDrums == 0) { mNumDrumNotes--; } else if (replacedNote.NumDrums == 0 && aNote.NumDrums > 0) { mNumDrumNotes++; } // See if we need to update the highest/lowest pitch. if (replacedNote.NumPitches > 0 && mNumMusicalNotes > 0) { // See if we need to set a new highest/lowest pitch. foreach (Music.PITCH pitch in replacedNote.MusicalNote.Pitches) { if (pitch != Music.PITCH.REST && pitch == mHighestPitch) { mHighestPitch = Music.PITCH.C0; SearchForHighestPitch(); } if (pitch != Music.PITCH.REST && pitch == mLowestPitch) { mLowestPitch = Music.PITCH.B9; SearchForLowestPitch(); } } } // Update the Song type. UpdateSongType(); }
/** * @brief Removes a note from the Song. * @param[in] aIndex The index of the note to remove. */ public void RemoveNote(int aIndex) { Assert.IsTrue(aIndex < mNotes.Count, "Tried to remove a note at the index " + aIndex.ToString() + " from a Song with only " + mNotes.Count.ToString() + " notes in it!"); // Get the note to be removed. Music.CombinedNote removedNote = mNotes[aIndex]; // Remove the note. mNotes.RemoveAt(aIndex); // Update information about the pitches in the Song.. if (removedNote.MusicalNote.Pitches != null) { // Decrease the number of musical notes. mNumMusicalNotes--; // See if we need to set a new highest/lowest pitch. foreach (Music.PITCH pitch in removedNote.MusicalNote.Pitches) { if (pitch != Music.PITCH.REST && pitch == mHighestPitch) { mHighestPitch = Music.PITCH.C0; SearchForHighestPitch(); } if (pitch != Music.PITCH.REST && pitch == mLowestPitch) { mLowestPitch = Music.PITCH.B9; SearchForLowestPitch(); } } } // Update information about the drums in the Song. if (removedNote.Drums.Hits != null) { mNumDrumNotes--; } // Update the Song type. UpdateSongType(); // Clean up. GC.Collect(); Resources.UnloadUnusedAssets(); }
/** * @brief Gets the raw audio data of each built-in dynamics value for a given @link Music::PITCH pitch@endlink. * @param[in] aPitch The @link Music::PITCH pitch@endlink for which the data is retrieved. * @return a 2-D array of floats where the indices correspond to [@link DefBID BuiltInDynamicsIndex@endlink][WaveformSample]. * * The array is retrived by mapping the given @link Music::PITCH pitch@endlink to the second index of the mAudioData member. */ public float[][] GetAudioDataForPitch(Music.PITCH aPitch) { Assert.IsTrue((int)aPitch >= (int)mLowestSupportedPitch && (int)aPitch <= (int)mHighestSupportedPitch, "Tried to load the audio data for the note " + Music.NoteToString(aPitch) + " which is not a note that is supported by this instrument!"); Assert.IsNotNull(mAudioData, "Tried to get data from a non-loaded virtual instrument!"); // If we need to load the clip, then load it. if (mAudioData[0][(int)aPitch] == null) { LoadAudioClipForPitch(aPitch); } // Declare the returned array. float[][] data = null; // If there aren't any built-in dynamics, then use a hard-coded index and copy the data to the array from the audio clip. if (mNumBuiltInDynamics == 0) { data = new float[1][]; int dataLength = mAudioData[0][(int)aPitch].Length; data[0] = new float[dataLength]; for (int i = 0; i < dataLength; i++) { data[0][i] = mAudioData[0][(int)aPitch][i]; } } // If there are built-in dynamics, then get the data from the audio clips for each corresponding file. else { data = new float[mNumBuiltInDynamics][]; int dataLength = 0; for (int i = 0; i < mNumBuiltInDynamics; i++) { dataLength = mAudioData[i][(int)aPitch].Length; data[i] = new float[dataLength]; for (int j = 0; j < dataLength; j++) { data[i][j] = mAudioData[i][(int)aPitch][j]; } } } return(data); }
/** * @brief Handles @link VirtualInstrumentManager::ChangeNoteRangeEvent a change in the note range@endlink. * @param[in] aNewLowestPitch The lowest pitch of the new range. * * Sets the @link KeyContainer::mRepresentedPitches represented pitches@endlink and loads a new part of the keyboard for a changed note range. * @sa VirtualInstrumentManager::ChangeNoteRangeEvent Music::PITCH */ private void HandleChangeNoteRangeEvent(Music.PITCH aNewLowestPitch) { // Clear the keyboard. ClearKeyboard(); mNumWhiteKeys = 0; // Update the represented pitches. for (int i = 0; i < NumberOfKeysToLoad; i++) { mRepresentedPitches[i] = (Music.PITCH)(i + (int)aNewLowestPitch); if (!Music.IsPitchABlackKey(mRepresentedPitches[i])) { mNumWhiteKeys++; } } // Load the keys. LoadKeys(); }
/** * @brief Sets up the SC_PitchDrumDisplayPanel as a display panel for @link Music::PITCH pitches@endlink. * @param[in] aPitch The @link Music::PITCH pitch@endlink that is being represented. * @param[in] aLength @copydoc SC_PitchDrumDisplayPanel::mLength * @param[in] aNoteVelocity @copydoc SC_PitchDrumDisplayPanel::mNoteVelocity */ public void InitializeAsPitchDisplay(Music.PITCH aPitch, Music.NoteLength aLength, int aNoteVelocity) { // Set that the panel is not representing drums. mIsDrum = false; // Set the pitch. mPitch = aPitch; // Set the pitch display text. mPitchDisplay.text = "Pitch:\n" + Music.NoteToString(aPitch); // Set the length. mLength = aLength; // See if we need to set the length image.. if (aLength.BaseLength == Music.NOTE_LENGTH_BASE.NONE) { mLengthPanel.SetActive(false); mDotModifierPanel.SetActive(false); mTripletModifierPanel.SetActive(false); } else { // Set the length image. mLengthPanel.transform.GetChild(1).GetComponent <Image>().sprite = Music.GetImageForNoteLength(aLength.BaseLength); // Set the modifier images. if (!aLength.Dot) { mDotModifierPanel.SetActive(false); } if (!aLength.Triplet) { mTripletModifierPanel.SetActive(false); } } // Set the velocity. mNoteVelocity = aNoteVelocity; mNoteVelocityHandler.SetValue(aNoteVelocity); }
/** * @brief Searches through the notes for the lowest pitch. * * This function is called when a note that contains the lowest * pitch in the Song is removed or replaced. We have to then * find the new lowest pitch in the Song. */ private void SearchForLowestPitch() { // Look through each note. foreach (Music.CombinedNote note in mNotes) { // If the note has pitches, then see if it has the lowest pitch. if (note.MusicalNote.Pitches != null) { // Look through each pitch in the note. foreach (Music.PITCH pitch in note.MusicalNote.Pitches) { // Update the lowest pitch if needed. if (pitch != Music.PITCH.REST && pitch < mLowestPitch) { mLowestPitch = pitch; } } } } }
public void OnEndDrag(PointerEventData aPointerData) { mValue = (Music.PITCH)mSlider.value; mText.text = Music.NoteToString(mValue); mVIM.ChangeNoteRange.Invoke(mValue); }
public void OnSliderValueChanged(float aNewValue) { mValue = (Music.PITCH)aNewValue; mText.text = Music.NoteToString(mValue); }
/*************************************************************************//** * @} * @defgroup VIBasePrivFunc Private Functions * @ingroup VIBase * Functions common to all types of instruments. * @{ ****************************************************************************/ /** * @brief Normalizes the data from the audio clips and puts them in the mAudioData member variable. * @param[in] aPitch The pitch that corresponds to the audio clips. * @param[in] aClips The clips to normalize. * * This function makes it so that the waveforms of the samples used for the instrument * have a peak of -0.1. If @link DefBID Built-In Dynamics@endlink are supported, then the peak is multiplied * by a ratio of the @link DefBIDThresh Built-In Dynamics thresholds@endlink. * For example, waveforms that are used for @link DefVel velocities@endlink from 50-75 would have a peak * that is 75% of the peak of the waveforms used for @link DefVel velocities@endlink from 76-100. */ private void NormalizeAudioClipsForPitch(Music.PITCH aPitch, AudioClip[] aClips) { // Make some temp variables for iterating through the data in the clips. float max = 0f; float[] dataFromClip = null; int clipLength = 0; int pitchIndex = (int)aPitch; // If the instrument doesn't support Built-In Dynamics, then the array is actually just one clip. if (mNumBuiltInDynamics == 0) { // Get the length of the clip. clipLength = aClips[0].samples; // Get the data of the clip dataFromClip = new float[clipLength]; aClips[0].GetData(dataFromClip, 0); // Allocate a place in the audio data container. mAudioData[0][pitchIndex] = new float[clipLength]; // Get the max value of this clip's data. for (int i = 0; i < clipLength; i++) { if (Mathf.Abs(dataFromClip[i]) > max) { max = Mathf.Abs(dataFromClip[i]); } } // Set the normalize factor. float normalizeFactor = NORMALIZED_PEAK / max; // Normalize the clip and put it in the audio data container for (int i = 0; i < clipLength; i++) { mAudioData[0][pitchIndex][i] = dataFromClip[i] * normalizeFactor; } } // If the instrument does support Built-In Dynamics, then we need to iterate through each // file associated with the clip. else { // Set up the normalized peaks for each Built-In Dynamic. float[] normalizedPeaks = new float[mNumBuiltInDynamics]; for (int i = 0; i < mNumBuiltInDynamics; i++) { normalizedPeaks[i] = NORMALIZED_PEAK * ((float)mBuiltInDynamicsThresholds[i] / (float)mBuiltInDynamicsThresholds[mNumBuiltInDynamics - 1]); } // Iterate through each clip associated with the pitch. for (int i = 0; i < mNumBuiltInDynamics; i++) { // Get the length of the clip data. clipLength = aClips[i].samples; // Initialize the clip's spot in the audio data container. mAudioData[i][pitchIndex] = new float[clipLength]; // Get the clip's data dataFromClip = new float[clipLength]; aClips[i].GetData(dataFromClip, 0); // Get the max value of the clip data. for (int j = 0; j < clipLength; j++) { if (Mathf.Abs(dataFromClip[j]) > max) { max = Mathf.Abs(dataFromClip[j]); } } // Get the normalize factor for this clip. float normalizeFactor = normalizedPeaks[i] / max; // Normalize the clip and put the normalized data into the audio data array. for (int j = 0; j < clipLength; j++) { mAudioData[i][pitchIndex][j] = dataFromClip[j] * normalizeFactor; } // Reset the max value for the next clip. max = 0f; } } }
/** * @brief Gets whether or not the instrument can play a specific @link Music::PITCH pitch@endlink. * @param[in] aPitch The @link Music::PITCH pitch@endlink that is being checked. * @return True if the @link Music::PITCH pitch@endlink can be played by the instrument. False otherwise. */ public bool IsPitchSupported(Music.PITCH aPitch) { return((int)aPitch >= (int)mLowestSupportedPitch && (int)aPitch <= (int)mHighestSupportedPitch); }
/** * @brief Handles the done button being clicked. */ private void OnDoneButtonClicked() { // Set up the pitches. int numPitches = mPitches.Count; Music.PITCH[] pitches = null; int[] pitchVelocities = null; Music.NoteLength[] lengths = null; // If there are pitches, then get them from the panels. if (numPitches != 0) { // Get all of the pitches, velocities, and lengths. pitches = new Music.PITCH[numPitches]; pitchVelocities = new int[numPitches]; lengths = new Music.NoteLength[numPitches]; int index = 0; foreach (SC_PitchDrumDisplayPanel pitch in mPitches) { pitches[index] = pitch.GetPitch(); pitchVelocities[index] = pitch.GetNoteVelocity(); lengths[index] = pitch.GetLengthOfPitch(); index++; } } // Create the melody note. Music.MelodyNote melody = new Music.MelodyNote(pitchVelocities, lengths, pitches); // Set up the drums. int numDrums = mDrums.Count; Music.DRUM[] drums = null; int[] drumVelocities = null; // If there are drums, then get them from the panels. if (numDrums != 0) { // Get all of the drums and their velocities. drums = new Music.DRUM[numDrums]; drumVelocities = new int[numDrums]; int index = 0; foreach (SC_PitchDrumDisplayPanel drum in mDrums) { drums[index] = drum.GetDrum(); drumVelocities[index] = drum.GetNoteVelocity(); index++; } } // Create the percussion note. Music.PercussionNote percussion = new Music.PercussionNote(drumVelocities, drums); // Get the offset of the note. Music.NoteLength offset = mOffsetPanel.GetSelected(); // Create the note. Music.CombinedNote note = new Music.CombinedNote(melody, percussion, offset); // Invoke the event which signals the dialog being finished. NoteDialogFinished.Invoke(note); // Self-destruct. DestroyImmediate(transform.parent.gameObject, false); }