/// <summary> /// Return note length as https://en.wikipedia.org/wiki/Note_value [New 1.9] /// </summary> /// <param name="note"></param> /// <returns></returns> public MidiNote.EnumLength NoteLength(MidiNote note) { if (miditoplay != null) { return(miditoplay.NoteLength(note)); } return(MidiNote.EnumLength.Sixteenth); }
/// <summary> /// Convert a MPTKNote to a Midi Note - [New 1.7] /// </summary> /// <returns></returns> public MidiNote ToMidiNote() { midinote = new MidiNote() { Midi = Note <= 0 ? 0 : (Note > 127 ? 127 : Note), Delay = Delay <= 0 ? 0f : (Delay > 1000f ? 1000f : Delay), Patch = Drum ? 0 : Patch <= 0 ? 0 : (Patch > 127 ? 127 : Patch), Drum = Drum, Pan = Pan, Duration = Duration <= 0 ? 200f : (Duration > 100000f ? 200f : Duration), Velocity = Velocity, }; return(midinote); }
private void LogInfoSample(MidiNote note, ImSample sample, string info = null) { //TimeSpan playtime = TimeSpan.Zero; //if (miditoplay != null) playtime = miditoplay.CurrentMidiTime(); //string time = string.Format("[{0:00}:{1:00}:{2:00}:{3:000}]", playtime.Hours, playtime.Minutes, playtime.Seconds, playtime.Milliseconds); string time = ""; if (sample != null) #if DEBUGPITCHNOTE { Debug.Log(string.Format("{0} C:{1,2} P:{2,3:000} D:{3} Note:{4,3:000} OriginalPitch:{5} PitchCorrection:{6} FineTune:{7} CoarseTune:{8} Duration:{9,4} sec. Velocity:{10} Wave:{11}", time, note.Chanel, note.Patch, note.Drum, note.Midi, sample.OriginalPitch, sample.PitchCorrection, sample.FineTune, sample.CoarseTune, Math.Round(note.Duration / 1000d, 2), note.Velocity, sample.WaveFile)); } #else { Debug.Log(string.Format("{0} C:{1,2} P:{2,3:000} D:{3} Note:{4,3:000} {5,-3} Duration:{6,4} sec. Velocity:{7} Pan:{8} Wave:{9}", time, note.Channel, note.Patch, note.Drum, note.Midi, HelperNoteLabel.LabelFromMidi(note.Midi), Math.Round(note.Duration / 1000d, 2), note.Velocity, sample.Pan, sample.WaveFile)); }
/// <summary> /// https://en.wikipedia.org/wiki/Note_value /// </summary> /// <param name="note"></param> /// <returns></returns> public MidiNote.EnumLength NoteLength(MidiNote note) { if (midifile != null) { if (note.Length >= midifile.DeltaTicksPerQuarterNote * 4) { return(MidiNote.EnumLength.Whole); } else if (note.Length >= midifile.DeltaTicksPerQuarterNote * 2) { return(MidiNote.EnumLength.Half); } else if (note.Length >= midifile.DeltaTicksPerQuarterNote) { return(MidiNote.EnumLength.Quarter); } else if (note.Length >= midifile.DeltaTicksPerQuarterNote / 2) { return(MidiNote.EnumLength.Eighth); } } return(MidiNote.EnumLength.Sixteenth); }
public List <MidiNote> ReadMidiEvents(double timeFromStartMS) { List <MidiNote> notes = null; try { EndMidiEvent = false; if (midifile != null) { if (NextPosEvent < MidiSorted.Count) { // The BPM measures how many quarter notes happen in a minute. To work out the length of each pulse we can use the following formula: // Pulse Length = 60 / (BPM * PPQN) // Calculate current pulse to play CurrentPulse += Convert.ToInt64((timeFromStartMS - LastTimeFromStartMS) / PulseLengthMs); LastTimeFromStartMS = timeFromStartMS; // From the last position played for (int currentPosEvent = NextPosEvent; currentPosEvent < MidiSorted.Count; currentPosEvent++) { TrackMidiEvent trackEvent = MidiSorted[currentPosEvent]; if (Quantization != 0) { trackEvent.AbsoluteQuantize = ((trackEvent.Event.AbsoluteTime + Quantization / 2) / Quantization) * Quantization; } else { trackEvent.AbsoluteQuantize = trackEvent.Event.AbsoluteTime; } //Debug.Log("ReadMidiEvents - timeFromStartMS:" + Convert.ToInt32(timeFromStartMS) + " LastTimeFromStartMS:" + Convert.ToInt32(LastTimeFromStartMS) + " CurrentPulse:" + CurrentPulse + " AbsoluteQuantize:" + trackEvent.AbsoluteQuantize); if (trackEvent.AbsoluteQuantize <= CurrentPulse) { NextPosEvent = currentPosEvent + 1; if (trackEvent.Event.CommandCode == MidiCommandCode.NoteOn) { if (((NoteOnEvent)trackEvent.Event).OffEvent != null) { NoteOnEvent noteon = (NoteOnEvent)trackEvent.Event; // if (noteon.OffEvent != null) { if (notes == null) { notes = new List <MidiNote>(); } //Debug.Log(string.Format("Track:{0} NoteNumber:{1,3:000} AbsoluteTime:{2,6:000000} NoteLength:{3,6:000000} OffDeltaTime:{4,6:000000} ", track, noteon.NoteNumber, noteon.AbsoluteTime, noteon.NoteLength, noteon.OffEvent.DeltaTime)); MidiNote note = new MidiNote() { AbsoluteQuantize = trackEvent.AbsoluteQuantize, Midi = noteon.NoteNumber, Channel = trackEvent.Event.Channel, Velocity = noteon.Velocity, Duration = noteon.NoteLength * PulseLengthMs, Length = noteon.NoteLength, Patch = PatchChanel[trackEvent.Event.Channel - 1], Drum = (trackEvent.Event.Channel == 10), Delay = 0, Pan = EnablePanChange ? PanChanel[trackEvent.Event.Channel - 1] : -1, }; if (VolumeChanel[note.Channel - 1] != 127) { note.Velocity = Mathf.RoundToInt(((float)note.Velocity) * ((float)VolumeChanel[trackEvent.Event.Channel - 1]) / 127f); } notes.Add(note); if (LogEvents) { Debug.Log(BuildInfoTrack(trackEvent) + string.Format("{0,-4} {1,3:000} Lenght:{2} {3} Veloc:{4}", noteon.NoteName, noteon.NoteNumber, noteon.NoteLength, NoteLength(note), noteon.Velocity)); } } } } else if (trackEvent.Event.CommandCode == MidiCommandCode.NoteOff) { // no need, noteoff are associated with noteon } else if (trackEvent.Event.CommandCode == MidiCommandCode.ControlChange) { ControlChangeEvent controlchange = (ControlChangeEvent)trackEvent.Event; if (controlchange.Controller == MidiController.Expression) { VolumeChanel[trackEvent.Event.Channel - 1] = controlchange.ControllerValue; } else if (controlchange.Controller == MidiController.MainVolume) { VolumeChanel[trackEvent.Event.Channel - 1] = controlchange.ControllerValue; } else if (controlchange.Controller == MidiController.Pan) { PanChanel[trackEvent.Event.Channel - 1] = controlchange.ControllerValue; } // Other midi event if (LogEvents) { Debug.Log(BuildInfoTrack(trackEvent) + string.Format("Control {0} {1}", controlchange.Controller, controlchange.ControllerValue)); } } else if (trackEvent.Event.CommandCode == MidiCommandCode.PatchChange) { PatchChangeEvent change = (PatchChangeEvent)trackEvent.Event; PatchChanel[trackEvent.Event.Channel - 1] = trackEvent.Event.Channel == 10 ? 0 : change.Patch; if (LogEvents) { Debug.Log(BuildInfoTrack(trackEvent) + string.Format("Patch {0,3:000} {1}", change.Patch, PatchChangeEvent.GetPatchName(change.Patch))); } } else if (trackEvent.Event.CommandCode == MidiCommandCode.MetaEvent) { MetaEvent meta = (MetaEvent)trackEvent.Event; switch (meta.MetaEventType) { case MetaEventType.SetTempo: if (EnableChangeTempo) { TempoEvent tempo = (TempoEvent)meta; //NewQuarterPerMinuteValue = tempo.Tempo; ChangeTempo(tempo.Tempo); //if (LogEvents)Debug.Log(BuildInfoTrack(trackEvent) + string.Format("SetTempo {0} MicrosecondsPerQuarterNote:{1}", tempo.Tempo, tempo.MicrosecondsPerQuarterNote)); } break; case MetaEventType.SequenceTrackName: if (!string.IsNullOrEmpty(SequenceTrackName)) { SequenceTrackName += "\n"; } SequenceTrackName += string.Format("T{0,2:00} {1}", trackEvent.IndexTrack, ((TextEvent)meta).Text); if (LogEvents) { Debug.Log(BuildInfoTrack(trackEvent) + string.Format("Sequence '{0}'", ((TextEvent)meta).Text)); } break; case MetaEventType.ProgramName: ProgramName += ((TextEvent)meta).Text + " "; if (LogEvents) { Debug.Log(BuildInfoTrack(trackEvent) + string.Format("Program '{0}'", ((TextEvent)meta).Text)); } break; case MetaEventType.TrackInstrumentName: if (!string.IsNullOrEmpty(TrackInstrumentName)) { TrackInstrumentName += "\n"; } TrackInstrumentName += string.Format("T{0,2:00} {1}", trackEvent.IndexTrack, ((TextEvent)meta).Text); if (LogEvents) { Debug.Log(BuildInfoTrack(trackEvent) + string.Format("Text '{0}'", ((TextEvent)meta).Text)); } break; case MetaEventType.TextEvent: TextEvent += ((TextEvent)meta).Text + " "; if (LogEvents) { Debug.Log(BuildInfoTrack(trackEvent) + string.Format("Sequence '{0}'", ((TextEvent)meta).Text)); } break; case MetaEventType.Copyright: Copyright += ((TextEvent)meta).Text + " "; if (LogEvents) { Debug.Log(BuildInfoTrack(trackEvent) + string.Format("Copyright '{0}'", ((TextEvent)meta).Text)); } break; case MetaEventType.Lyric: // lyric case MetaEventType.Marker: // marker case MetaEventType.CuePoint: // cue point case MetaEventType.DeviceName: break; } //Debug.Log(BuildInfoTrack(trackEvent) + string.Format("Meta {0} {1}", meta.MetaEventType, meta.ToString())); } else { // Other midi event //Debug.Log(string.Format("Track:{0} Channel:{1,2:00} CommandCode:{2,3:000} AbsoluteTime:{3,6:000000}", track, e.Channel, e.CommandCode.ToString(), e.AbsoluteTime)); } } else { // Out of time, exit for loop break; } } if (notes != null) { //if (CancelNextReadEvents) //{ // notes = null; // //Debug.Log("CancelNextReadEvents"); // CancelNextReadEvents = false; //} //else //if (notes.Count > 3 && (notes[notes.Count - 1].AbsoluteQuantize - notes[0].AbsoluteQuantize) > midifile.DeltaTicksPerQuarterNote * 8) //{ // //notes.RemoveRange(0, notes.Count - 1); // Debug.Log("--> Too much notes " + notes.Count + " timeFromStartMS:" + Convert.ToInt32(timeFromStartMS) + " Start:" + notes[0].AbsoluteQuantize + " Ecart:" + (notes[notes.Count - 1].AbsoluteQuantize - notes[0].AbsoluteQuantize) + " CurrentPulse:" + CurrentPulse); // //notes = null; //} } } else { // End of midi events EndMidiEvent = true; } } } catch (System.Exception ex) { MidiPlayerGlobal.ErrorDetail(ex); } return(notes); }
/// <summary> /// Play one note with this AudioSource /// </summary> /// <param name="audio">AudioSource</param> /// <param name="loop">Sound with loop</param> /// <param name="note"></param> /// <returns></returns> protected IEnumerator PlayNote(AudioSource audio, bool drum, ImSample sample, MidiNote note, float vTransitionTime) { if (note.Delay > 0f) { float endDelay = Time.realtimeSinceStartup + note.Delay / 1000f; while (Time.realtimeSinceStartup < endDelay && note.Delay > 0f) { yield return(new WaitForSeconds(0.01f)); } } try { audio.pitch = note.Pitch; if (drum) { audio.loop = false; } else { audio.loop = sample.IsLoop; } // Attenuation removed from current version //audio.volume = Mathf.Lerp(0f, 1f, note.Velocity * sample.Attenuation / 127f) * MPTK_Volume; audio.volume = Mathf.Lerp(0f, 1f, note.Velocity / 127f) * MPTK_Volume; // Pan from the SoundFont if (MidiPlayerGlobal.CurrentMidiSet.ActiveSounFontInfo.Panoramic) { audio.panStereo = Mathf.Lerp(-1f, 1f, (sample.Pan + 500) / 1000f); } else { audio.panStereo = 0f; } // Pan from the midi file or from a midi generated event if (note.Pan >= 0) { audio.panStereo = Mathf.Lerp(-1f, 1f, note.Pan / 127f); } //Debug.Log(string.Format(" Pan - note:{0,3} sample:{1,3} --> audio pan:{2,3}" , note.Pan , sample.Pan,Math.Round( audio.panStereo,2))); //Debug.Log(string.Format(" Vel - note:{0,3} sample:{1,3} --> audio vel:{2,3}", note.Velocity , Math.Round(sample.Attenuation, 2), Math.Round(audio.volume, 2))); //Debug.Log(string.Format(" Loop - drum:{0} sample:{1} --> audio loop:{2}" , drum , sample.IsLoop , audio.loop)); audio.Play(); } catch (Exception) { } // Attack & Decay taken in account by the wave, for drum (loop=false) play the wave one shot (no duration) if (audio.loop) { // Sustain phase until key release, constant amplitude float end = Time.realtimeSinceStartup + (float)(note.Duration / 1000d); while (note.Duration > 0f) { try { if (Time.realtimeSinceStartup >= end || !audio.isPlaying) { break; } } catch (Exception) { } yield return(new WaitForSeconds(0.01f)); } //Debug.Log("stop " + sample.WaveFile + " " + note.Duration); // Release phase if (vTransitionTime > 0f) { float dtime = 0f; float volume = 0; try { volume = audio.volume; end = Time.realtimeSinceStartup + vTransitionTime; } catch (Exception) { } do { dtime = end - Time.realtimeSinceStartup; try { audio.volume = Mathf.Lerp(0f, volume, dtime / vTransitionTime); if (dtime < 0f || !audio.isPlaying) { break; } } catch (Exception) { break; } yield return(new WaitForSeconds(0.01f)); }while (true); } try { audio.Stop(); } catch (Exception) { } } //else //{ // // play with no loop (drum) // while (audio.isPlaying) // { // yield return new WaitForSeconds(0.01f); // } //} }
/// <summary> /// Play one note - [New 1.7] /// </summary> /// <param name="note"></param> public void MPTK_PlayNote(MidiNote note) { try { // Search sample associated to the preset, midi note and velocity int selectedBank = note.Drum ? MidiPlayerGlobal.CurrentMidiSet.ActiveSounFontInfo.DrumKitBankNumber : selectedBank = MidiPlayerGlobal.CurrentMidiSet.ActiveSounFontInfo.DefaultBankNumber; int noteMidi = note.Midi; if (!note.Drum) { noteMidi += MPTK_Transpose; } //ImSample smpl = MidiPlayerGlobal.GetImSample(selectedBank, note.Patch, noteMidi, note.Velocity); //if (smpl != null) { List <ImSample> samples = MidiPlayerGlobal.GetImMultiSample(selectedBank, note.Patch, noteMidi, note.Velocity); //LogInfoSample(note, null, " Found " + samples.Count + " samples"); foreach (ImSample smpl in samples) { note.Pitch = Mathf.Pow(_ratioHalfTone, (float)(noteMidi - smpl.OriginalPitch + smpl.CoarseTune) + (float)smpl.FineTune / 100f); // Load wave from audioclip AudioClip clip = DicAudioClip.Get(smpl.WaveFile); if (clip != null && clip.loadState == AudioDataLoadState.Loaded) { if (MPTK_LogWaves) { LogInfoSample(note, smpl); } AudioSource audioSelected = null; // Search audioclip not playing with the same wave try { foreach (AudioSource audio in audiosources) { //Debug.Log(audio.isPlaying + " " + audio.clip.name + " " + clip.name); if (!audio.isPlaying && audio.clip.name == clip.name) { audioSelected = audio; break; } } } catch (System.Exception ex) { MidiPlayerGlobal.ErrorDetail(ex); } if (audioSelected == null) { // No audiosource available, create a new audiosource audioSelected = Instantiate <AudioSource>(AudioSourceTemplate); audioSelected.Stop(); audioSelected.transform.position = AudioSourceTemplate.transform.position; audioSelected.transform.SetParent(this.transform); audiosources.Add(audioSelected); // Assign sound to audioclip audioSelected.clip = clip; } // Play note StartCoroutine(PlayNote(audioSelected, note.Drum, smpl, note, timeToRelease)); } else { if (MPTK_LogWaves) { LogInfoSample(note, null, smpl.WaveFile + " ******** Clip not ready to play or not found ******"); } } } //else if (samples.Count == 0) { if (MPTK_LogWaves) { LogInfoSample(note, null, " ********* Sample not found *********"); } } } } catch (System.Exception ex) { MidiPlayerGlobal.ErrorDetail(ex); } }