public void TestNotesOverlap() { SheetMusic.SetNoteSize(false); KeySignature key = new KeySignature(0, 0); int quarter = 400; TimeSignature time = new TimeSignature(4, 4, quarter, 60000); int num1 = WhiteNote.BottomTreble.Number(); int num2 = num1 + 1; MidiNote note1 = new MidiNote(0, 0, num1, quarter); MidiNote note2 = new MidiNote(0, 0, num2, quarter); List<MidiNote> notes = new List<MidiNote>(2); notes.Add(note1); notes.Add(note2); ChordSymbol chord = new ChordSymbol(notes, key, time, Clef.Treble, null); Assert.AreEqual(chord.ToString(), "ChordSymbol clef=Treble start=0 end=400 width=25 hastwostems=False AccidSymbol accid=Sharp whitenote=F4 clef=Treble width=9 Note whitenote=F4 duration=Quarter leftside=True Note whitenote=F4 duration=Quarter leftside=True Stem duration=Quarter direction=1 top=F4 bottom=F4 end=E5 overlap=False side=2 width_to_pair=0 receiver_in_pair=False "); }
public void TestRoundStartTimes() { const byte notenum = 20; List<MidiTrack> tracks = new List<MidiTrack>(); MidiTrack track1 = new MidiTrack(0); track1.AddNote(new MidiNote(0, 0, notenum, 60)); track1.AddNote(new MidiNote(3, 0, notenum+1, 60)); track1.AddNote(new MidiNote(15, 0, notenum+2, 60)); track1.AddNote(new MidiNote(22, 0, notenum+3, 60)); track1.AddNote(new MidiNote(62, 0, notenum+4, 60)); MidiTrack track2 = new MidiTrack(1); track2.AddNote(new MidiNote(2, 0, notenum+10, 60)); track2.AddNote(new MidiNote(10, 0, notenum+11, 60)); track2.AddNote(new MidiNote(20, 0, notenum+12, 60)); track2.AddNote(new MidiNote(35, 0, notenum+13, 60)); track2.AddNote(new MidiNote(36, 0, notenum+14, 60)); tracks.Add(track1); tracks.Add(track2); int quarter = 130; int tempo = 500000; TimeSignature time = new TimeSignature(4, 4, quarter, tempo); /* quarternote * 60,000 / 500,000 = 15 pulses * So notes within 15 pulses should be grouped together. * 0, 2, 3, 10, 15 are grouped to starttime 0 * 20, 22, 35 are grouped to starttime 20 * 36 is still 36 * 62 is still 62 */ MidiFile.RoundStartTimes(tracks, 60, time); List<MidiNote> notes1 = tracks[0].Notes; List<MidiNote> notes2 = tracks[1].Notes; Assert.AreEqual(notes1.Count, 5); Assert.AreEqual(notes2.Count, 5); Assert.AreEqual(notes1[0].Number, notenum); Assert.AreEqual(notes1[1].Number, notenum+1); Assert.AreEqual(notes1[2].Number, notenum+2); Assert.AreEqual(notes1[3].Number, notenum+3); Assert.AreEqual(notes1[4].Number, notenum+4); Assert.AreEqual(notes2[0].Number, notenum+10); Assert.AreEqual(notes2[1].Number, notenum+11); Assert.AreEqual(notes2[2].Number, notenum+12); Assert.AreEqual(notes2[3].Number, notenum+13); Assert.AreEqual(notes2[4].Number, notenum+14); Assert.AreEqual(notes1[0].StartTime, 0); Assert.AreEqual(notes1[1].StartTime, 0); Assert.AreEqual(notes1[2].StartTime, 0); Assert.AreEqual(notes1[3].StartTime, 20); Assert.AreEqual(notes1[3].StartTime, 20); Assert.AreEqual(notes1[4].StartTime, 62); Assert.AreEqual(notes2[0].StartTime, 0); Assert.AreEqual(notes2[1].StartTime, 0); Assert.AreEqual(notes2[2].StartTime, 20); Assert.AreEqual(notes2[3].StartTime, 20); Assert.AreEqual(notes2[4].StartTime, 36); }
public void TestSixteenthDuration() { SheetMusic.SetNoteSize(false); KeySignature key = new KeySignature(0, 0); int quarter = 400; TimeSignature time = new TimeSignature(4, 4, quarter, 60000); int num1 = WhiteNote.BottomTreble.Number(); MidiNote note1 = new MidiNote(0, 0, num1, quarter/4); List<MidiNote> notes = new List<MidiNote>(2); notes.Add(note1); ChordSymbol chord = new ChordSymbol(notes, key, time, Clef.Treble, null); Assert.AreEqual(chord.ToString(), "ChordSymbol clef=Treble start=0 end=100 width=16 hastwostems=False Note whitenote=F4 duration=Sixteenth leftside=True Stem duration=Sixteenth direction=1 top=F4 bottom=F4 end=G5 overlap=False side=2 width_to_pair=0 receiver_in_pair=False "); Assert.AreEqual(chord.AboveStaff, SheetMusic.NoteHeight); }
bool CanCreateBeam(ChordSymbol[] chords, TimeSignature time, bool startQuarter) { int numChords = chords.Length; Stem firstStem = chords[0].Stem; Stem lastStem = chords[chords.Length - 1].Stem; if (firstStem == null || lastStem == null) { return(false); } int measure = chords[0].StartTime / time.Measure; NoteDuration dur = firstStem.Duration; NoteDuration dur2 = lastStem.Duration; bool dotted8_to_16 = false; if (chords.Length == 2 && dur == NoteDuration.DottedEighth && dur2 == NoteDuration.Sixteenth) { dotted8_to_16 = true; } if (dur == NoteDuration.Whole || dur == NoteDuration.Half || dur == NoteDuration.DottedHalf || dur == NoteDuration.Quarter || dur == NoteDuration.DottedQuarter || (dur == NoteDuration.DottedEighth && !dotted8_to_16)) { return(false); } if (numChords == 6) { if (dur != NoteDuration.Eighth) { return(false); } bool correctTime = ((time.Numerator == 3 && time.Denominator == 4) || (time.Numerator == 6 && time.Denominator == 8) || (time.Numerator == 6 && time.Denominator == 4)); if (!correctTime) { return(false); } if (time.Numerator == 6 && time.Denominator == 4) { /* first chord must start at 1st or 4th quarter note */ int beat = time.Quarter * 3; if ((chords[0].StartTime % beat) > time.Quarter / 6) { return(false); } } } else if (numChords == 4) { if (time.Numerator == 3 && time.Denominator == 8) { return(false); } bool correctTime = (time.Numerator == 2 || time.Numerator == 4 || time.Numerator == 8); if (!correctTime && dur != NoteDuration.Sixteenth) { return(false); } /* chord must start on quarter note */ int beat = time.Quarter; if (dur == NoteDuration.Eighth) { /* 8th note chord must start on 1st or 3rd quarter note */ beat = time.Quarter * 2; } else if (dur == NoteDuration.ThirtySecond) { /* 32nd note must start on an 8th beat */ beat = time.Quarter / 2; } if ((chords[0].StartTime % beat) > time.Quarter / 6) { return(false); } } else if (numChords == 3) { bool valid = (dur == NoteDuration.Triplet) || (dur == NoteDuration.Eighth && time.Numerator == 12 && time.Denominator == 8); if (!valid) { return(false); } /* chord must start on quarter note */ int beat = time.Quarter; if (time.Numerator == 12 && time.Denominator == 8) { /* In 12/8 time, chord must start on 3*8th beat */ beat = time.Quarter / 2 * 3; } if ((chords[0].StartTime % beat) > time.Quarter / 6) { return(false); } } else if (numChords == 2) { if (startQuarter) { int beat = time.Quarter; if ((chords[0].StartTime % beat) > time.Quarter / 6) { return(false); } } } foreach (ChordSymbol chord in chords) { if ((chord.StartTime / time.Measure) != measure) { return(false); } if (chord.Stem == null) { return(false); } if (chord.Stem.Duration != dur && !dotted8_to_16) { return(false); } if (chord.Stem.isBeam) { return(false); } } /* Check that all stems can point in same direction */ bool hasTwoStems = false; int direction = Stem.Up; foreach (ChordSymbol chord in chords) { if (chord.HasTwoStems) { if (hasTwoStems && chord.Stem.Direction != direction) { return(false); } hasTwoStems = true; direction = chord.Stem.Direction; } } /* Get the final stem direction */ if (!hasTwoStems) { WhiteNote note1; WhiteNote note2; note1 = (firstStem.Direction == Stem.Up ? firstStem.Top : firstStem.Bottom); note2 = (lastStem.Direction == Stem.Up ? lastStem.Top : lastStem.Bottom); direction = StemDirection(note1, note2, chords[0].Clef); } /* If the notes are too far apart, don't use a beam */ if (direction == Stem.Up) { if (Math.Abs(firstStem.Top.Dist(lastStem.Top)) >= 11) { return(false); } } else { if (Math.Abs(firstStem.Bottom.Dist(lastStem.Bottom)) >= 11) { return(false); } } return(true); }
public bool useDefaultInstruments; /** If true, don't change instruments */ #endregion Fields #region Constructors public MidiOptions(MidiFile midifile) { int numtracks = midifile.Tracks.Count; tracks = new bool[numtracks]; mute = new bool[numtracks]; instruments = new int[numtracks]; for (int i = 0; i < tracks.Length; i++) { tracks[i] = true; mute[i] = false; instruments[i] = midifile.Tracks[i].Instrument; if (midifile.Tracks[i].InstrumentName == "Percussion") { tracks[i] = false; } } useDefaultInstruments = true; scrollVert = true; largeNoteSize = false; if (tracks.Length == 1) { twoStaffs = true; } else { twoStaffs = false; } showNoteLetters = NoteNameNone; showLyrics = true; showMeasures = false; shifttime = 0; transpose = 0; key = -1; time = midifile.Time; colors = null; shadeColor = Color.FromArgb(210, 205, 220); shade2Color = Color.FromArgb(80, 100, 250); combineInterval = 40; tempo = midifile.Time.Tempo; pauseTime = 0; playMeasuresInLoop = false; playMeasuresInLoopStart = 0; playMeasuresInLoopEnd = midifile.EndTime() / midifile.Time.Measure; }
private int width; /** The width of the chord */ #endregion Fields #region Constructors /** Create a new Chord Symbol from the given list of midi notes. * All the midi notes will have the same start time. Use the * key signature to get the white key and accidental symbol for * each note. Use the time signature to calculate the duration * of the notes. Use the clef when drawing the chord. */ public ChordSymbol(List<MidiNote> midinotes, KeySignature key, TimeSignature time, Clef c, SheetMusic sheet) { int len = midinotes.Count; int i; hastwostems = false; clef = c; sheetmusic = sheet; starttime = midinotes[0].StartTime; endtime = midinotes[0].EndTime; for (i = 0; i < midinotes.Count; i++) { if (i > 1) { if (midinotes[i].Number < midinotes[i-1].Number) { throw new System.ArgumentException("Chord notes not in increasing order by number"); } } endtime = Math.Max(endtime, midinotes[i].EndTime); } notedata = CreateNoteData(midinotes, key, time); accidsymbols = CreateAccidSymbols(notedata, clef); /* Find out how many stems we need (1 or 2) */ NoteDuration dur1 = notedata[0].duration; NoteDuration dur2 = dur1; int change = -1; for (i = 0; i < notedata.Length; i++) { dur2 = notedata[i].duration; if (dur1 != dur2) { change = i; break; } } if (dur1 != dur2) { /* We have notes with different durations. So we will need * two stems. The first stem points down, and contains the * bottom note up to the note with the different duration. * * The second stem points up, and contains the note with the * different duration up to the top note. */ hastwostems = true; stem1 = new Stem(notedata[0].whitenote, notedata[change-1].whitenote, dur1, Stem.Down, NotesOverlap(notedata, 0, change) ); stem2 = new Stem(notedata[change].whitenote, notedata[notedata.Length-1].whitenote, dur2, Stem.Up, NotesOverlap(notedata, change, notedata.Length) ); } else { /* All notes have the same duration, so we only need one stem. */ int direction = StemDirection(notedata[0].whitenote, notedata[notedata.Length-1].whitenote, clef); stem1 = new Stem(notedata[0].whitenote, notedata[notedata.Length-1].whitenote, dur1, direction, NotesOverlap(notedata, 0, notedata.Length) ); stem2 = null; } /* For whole notes, no stem is drawn. */ if (dur1 == NoteDuration.Whole) stem1 = null; if (dur2 == NoteDuration.Whole) stem2 = null; width = MinWidth; }
/** Parse the given Midi file, and return an instance of this MidiFile * class. After reading the midi file, this object will contain: * - The raw list of midi events * - The Time Signature of the song * - All the tracks in the song which contain notes. * - The number, starttime, and duration of each note. */ public void parse(MidiFileReader file, string filename) { string id; int len; this.filename = filename; tracks = new List<MidiTrack>(); trackPerChannel = false; id = file.ReadAscii(4); if (id != "MThd") { throw new MidiFileException("Doesn't start with MThd", 0); } len = file.ReadInt(); if (len != 6) { throw new MidiFileException("Bad MThd header", 4); } trackmode = file.ReadShort(); int num_tracks = file.ReadShort(); quarternote = file.ReadShort(); events = new List<MidiEvent>[num_tracks]; for (int tracknum = 0; tracknum < num_tracks; tracknum++) { events[tracknum] = ReadTrack(file); MidiTrack track = new MidiTrack(events[tracknum], tracknum); if (track.Notes.Count > 0) { tracks.Add(track); } } /* Get the length of the song in pulses */ foreach (MidiTrack track in tracks) { MidiNote last = track.Notes[track.Notes.Count-1]; if (this.totalpulses < last.StartTime + last.Duration) { this.totalpulses = last.StartTime + last.Duration; } } /* If we only have one track with multiple channels, then treat * each channel as a separate track. */ if (tracks.Count == 1 && HasMultipleChannels(tracks[0])) { tracks = SplitChannels(tracks[0], events[tracks[0].Number]); trackPerChannel = true; } CheckStartTimes(tracks); /* Determine the time signature */ int tempo = 0; int numer = 0; int denom = 0; foreach (List<MidiEvent> list in events) { foreach (MidiEvent mevent in list) { if (mevent.Metaevent == MetaEventTempo && tempo == 0) { tempo = mevent.Tempo; } if (mevent.Metaevent == MetaEventTimeSignature && numer == 0) { numer = mevent.Numerator; denom = mevent.Denominator; } } } if (tempo == 0) { tempo = 500000; /* 500,000 microseconds = 0.05 sec */ } if (numer == 0) { numer = 4; denom = 4; } timesig = new TimeSignature(numer, denom, quarternote, tempo); }
/** Add in the vertical bars delimiting measures. * Also, add the time signature symbols. */ private List<MusicSymbol> AddBars(List<ChordSymbol> chords, TimeSignature time, int lastStart) { List<MusicSymbol> symbols = new List<MusicSymbol>(); TimeSigSymbol timesig = new TimeSigSymbol(time.Numerator, time.Denominator); symbols.Add(timesig); /* The starttime of the beginning of the measure */ int measuretime = 0; int i = 0; while (i < chords.Count) { if (measuretime <= chords[i].StartTime) { symbols.Add(new BarSymbol(measuretime) ); measuretime += time.Measure; } else { symbols.Add(chords[i]); i++; } } /* Keep adding bars until the last StartTime (the end of the song) */ while (measuretime < lastStart) { symbols.Add(new BarSymbol(measuretime) ); measuretime += time.Measure; } /* Add the final vertical bar to the last measure */ symbols.Add(new BarSymbol(measuretime) ); return symbols; }
/** Connect chords of the same duration with a horizontal beam. * numChords is the number of chords per beam (2, 3, 4, or 6). * if startBeat is true, the first chord must start on a quarter note beat. */ private static void CreateBeamedChords(List<MusicSymbol>[] allsymbols, TimeSignature time, int numChords, bool startBeat) { int[] chordIndexes = new int[numChords]; ChordSymbol[] chords = new ChordSymbol[numChords]; foreach (List<MusicSymbol> symbols in allsymbols) { int startIndex = 0; while (true) { int horizDistance = 0; bool found = FindConsecutiveChords(symbols, time, startIndex, chordIndexes, ref horizDistance); if (!found) { break; } for (int i = 0; i < numChords; i++) { chords[i] = (ChordSymbol)symbols[ chordIndexes[i] ]; } if (ChordSymbol.CanCreateBeam(chords, time, startBeat)) { ChordSymbol.CreateBeam(chords, horizDistance); startIndex = chordIndexes[numChords-1] + 1; } else { startIndex = chordIndexes[0] + 1; } /* What is the value of startIndex here? * If we created a beam, we start after the last chord. * If we failed to create a beam, we start after the first chord. */ } } }
/** Find 2, 3, 4, or 6 chord symbols that occur consecutively (without any * rests or bars in between). There can be BlankSymbols in between. * * The startIndex is the index in the symbols to start looking from. * * Store the indexes of the consecutive chords in chordIndexes. * Store the horizontal distance (pixels) between the first and last chord. * If we failed to find consecutive chords, return false. */ private static bool FindConsecutiveChords(List<MusicSymbol> symbols, TimeSignature time, int startIndex, int[] chordIndexes, ref int horizDistance) { int i = startIndex; int numChords = chordIndexes.Length; while (true) { horizDistance = 0; /* Find the starting chord */ while (i < symbols.Count - numChords) { if (symbols[i] is ChordSymbol) { ChordSymbol c = (ChordSymbol) symbols[i]; if (c.Stem != null) { break; } } i++; } if (i >= symbols.Count - numChords) { chordIndexes[0] = -1; return false; } chordIndexes[0] = i; bool foundChords = true; for (int chordIndex = 1; chordIndex < numChords; chordIndex++) { i++; int remaining = numChords - 1 - chordIndex; while ((i < symbols.Count - remaining) && (symbols[i] is BlankSymbol)) { horizDistance += symbols[i].Width; i++; } if (i >= symbols.Count - remaining) { return false; } if (!(symbols[i] is ChordSymbol)) { foundChords = false; break; } chordIndexes[chordIndex] = i; horizDistance += symbols[i].Width; } if (foundChords) { return true; } /* Else, start searching again from index i */ } }
/** Connect chords of the same duration with a horizontal beam. * * We create beams in the following order: * - 6 connected 8th note chords, in 3/4, 6/8, or 6/4 time * - Triplets that start on quarter note beats * - 3 connected chords that start on quarter note beats (12/8 time only) * - 4 connected chords that start on quarter note beats (4/4 or 2/4 time only) * - 2 connected chords that start on quarter note beats * - 2 connected chords that start on any beat */ private static void CreateAllBeamedChords(List<MusicSymbol>[] allsymbols, TimeSignature time) { if ((time.Numerator == 3 && time.Denominator == 4) || (time.Numerator == 6 && time.Denominator == 8) || (time.Numerator == 6 && time.Denominator == 4) ) { CreateBeamedChords(allsymbols, time, 6, true); } CreateBeamedChords(allsymbols, time, 3, true); CreateBeamedChords(allsymbols, time, 4, true); CreateBeamedChords(allsymbols, time, 2, true); CreateBeamedChords(allsymbols, time, 2, false); }
/** Return the rest symbols needed to fill the time interval between * start and end. If no rests are needed, return nil. */ private RestSymbol[] GetRests(TimeSignature time, int start, int end) { RestSymbol[] result; RestSymbol r1, r2; if (end - start < 0) return null; NoteDuration dur = time.GetNoteDuration(end - start); switch (dur) { case NoteDuration.Whole: case NoteDuration.Half: case NoteDuration.Quarter: case NoteDuration.Eighth: r1 = new RestSymbol(start, dur); result = new RestSymbol[]{ r1 }; return result; case NoteDuration.DottedHalf: r1 = new RestSymbol(start, NoteDuration.Half); r2 = new RestSymbol(start + time.Quarter*2, NoteDuration.Quarter); result = new RestSymbol[]{ r1, r2 }; return result; case NoteDuration.DottedQuarter: r1 = new RestSymbol(start, NoteDuration.Quarter); r2 = new RestSymbol(start + time.Quarter, NoteDuration.Eighth); result = new RestSymbol[]{ r1, r2 }; return result; case NoteDuration.DottedEighth: r1 = new RestSymbol(start, NoteDuration.Eighth); r2 = new RestSymbol(start + time.Quarter/2, NoteDuration.Sixteenth); result = new RestSymbol[]{ r1, r2 }; return result; default: return null; } }
/** Given the chord symbols for a track, create a new symbol list * that contains the chord symbols, vertical bars, rests, and clef changes. * Return a list of symbols (ChordSymbol, BarSymbol, RestSymbol, ClefSymbol) */ private List<MusicSymbol> CreateSymbols(List<ChordSymbol> chords, ClefMeasures clefs, TimeSignature time, int lastStart) { List<MusicSymbol> symbols = new List<MusicSymbol>(); symbols = AddBars(chords, time, lastStart); symbols = AddRests(symbols, time); symbols = AddClefChanges(symbols, clefs, time); return symbols; }
public void TestStemUpBass() { SheetMusic.SetNoteSize(false); KeySignature key = new KeySignature(0, 0); int quarter = 400; TimeSignature time = new TimeSignature(4, 4, quarter, 60000); int num1 = WhiteNote.BottomBass.Number(); int num2 = num1 + 2; MidiNote note1 = new MidiNote(0, 0, num1, quarter); MidiNote note2 = new MidiNote(0, 0, num2, quarter); List<MidiNote> notes = new List<MidiNote>(2); notes.Add(note1); notes.Add(note2); ChordSymbol chord = new ChordSymbol(notes, key, time, Clef.Bass, null); Assert.AreEqual(chord.ToString(), "ChordSymbol clef=Bass start=0 end=400 width=16 hastwostems=False Note whitenote=A3 duration=Quarter leftside=True Note whitenote=B3 duration=Quarter leftside=False Stem duration=Quarter direction=1 top=B3 bottom=A3 end=A4 overlap=True side=2 width_to_pair=0 receiver_in_pair=False "); }
/** The current clef is always shown at the beginning of the staff, on * the left side. However, the clef can also change from measure to * measure. When it does, a Clef symbol must be shown to indicate the * change in clef. This function adds these Clef change symbols. * This function does not add the main Clef Symbol that begins each * staff. That is done in the Staff() contructor. */ private List<MusicSymbol> AddClefChanges(List<MusicSymbol> symbols, ClefMeasures clefs, TimeSignature time) { List<MusicSymbol> result = new List<MusicSymbol>( symbols.Count ); Clef prevclef = clefs.GetClef(0); foreach (MusicSymbol symbol in symbols) { /* A BarSymbol indicates a new measure */ if (symbol is BarSymbol) { Clef clef = clefs.GetClef(symbol.StartTime); if (clef != prevclef) { result.Add(new ClefSymbol(clef, symbol.StartTime-1, true)); } prevclef = clef; } result.Add(symbol); } return result; }
public void TestWholeDuration() { SheetMusic.SetNoteSize(false); KeySignature key = new KeySignature(0, 0); int quarter = 400; TimeSignature time = new TimeSignature(4, 4, quarter, 60000); int num1 = WhiteNote.BottomTreble.Number(); MidiNote note1 = new MidiNote(0, 0, num1, quarter*4); List<MidiNote> notes = new List<MidiNote>(2); notes.Add(note1); ChordSymbol chord = new ChordSymbol(notes, key, time, Clef.Treble, null); Assert.AreEqual(chord.ToString(), "ChordSymbol clef=Treble start=0 end=1600 width=16 hastwostems=False Note whitenote=F4 duration=Whole leftside=True "); }
/** Add rest symbols between notes. All times below are * measured in pulses. */ private List<MusicSymbol> AddRests(List<MusicSymbol> symbols, TimeSignature time) { int prevtime = 0; List<MusicSymbol> result = new List<MusicSymbol>( symbols.Count ); foreach (MusicSymbol symbol in symbols) { int starttime = symbol.StartTime; RestSymbol[] rests = GetRests(time, prevtime, starttime); if (rests != null) { foreach (RestSymbol r in rests) { result.Add(r); } } result.Add(symbol); /* Set prevtime to the end time of the last note/symbol. */ if (symbol is ChordSymbol) { ChordSymbol chord = (ChordSymbol)symbol; prevtime = Math.Max( chord.EndTime, prevtime ); } else { prevtime = Math.Max(starttime, prevtime); } } return result; }
/** In Midi Files, time is measured in pulses. Notes that have * pulse times that are close together (like within 10 pulses) * will sound like they're the same chord. We want to draw * these notes as a single chord, it makes the sheet music much * easier to read. We don't want to draw notes that are close * together as two separate chords. * * The SymbolSpacing class only aligns notes that have exactly the same * start times. Notes with slightly different start times will * appear in separate vertical columns. This isn't what we want. * We want to align notes with approximately the same start times. * So, this function is used to assign the same starttime for notes * that are close together (timewise). */ public static void RoundStartTimes(List<MidiTrack> tracks, int millisec, TimeSignature time) { /* Get all the starttimes in all tracks, in sorted order */ List<int> starttimes = new List<int>(); foreach (MidiTrack track in tracks) { foreach (MidiNote note in track.Notes) { starttimes.Add( note.StartTime ); } } starttimes.Sort(); /* Notes within "millisec" milliseconds apart will be combined. */ int interval = time.Quarter * millisec * 1000 / time.Tempo; /* If two starttimes are within interval millisec, make them the same */ for (int i = 0; i < starttimes.Count - 1; i++) { if (starttimes[i+1] - starttimes[i] <= interval) { starttimes[i+1] = starttimes[i]; } } CheckStartTimes(tracks); /* Adjust the note starttimes, so that it matches one of the starttimes values */ foreach (MidiTrack track in tracks) { int i = 0; foreach (MidiNote note in track.Notes) { while (i < starttimes.Count && note.StartTime - interval > starttimes[i]) { i++; } if (note.StartTime > starttimes[i] && note.StartTime - starttimes[i] <= interval) { note.StartTime = starttimes[i]; } } track.Notes.Sort(track.Notes[0]); } }
/** Create the chord symbols for a single track. * @param midinotes The Midinotes in the track. * @param key The Key Signature, for determining sharps/flats. * @param time The Time Signature, for determining the measures. * @param clefs The clefs to use for each measure. * @ret An array of ChordSymbols */ private List<ChordSymbol> CreateChords(List<MidiNote> midinotes, KeySignature key, TimeSignature time, ClefMeasures clefs) { int i = 0; List<ChordSymbol> chords = new List<ChordSymbol>(); List<MidiNote> notegroup = new List<MidiNote>(12); int len = midinotes.Count; while (i < len) { int starttime = midinotes[i].StartTime; Clef clef = clefs.GetClef(starttime); /* Group all the midi notes with the same start time * into the notes list. */ notegroup.Clear(); notegroup.Add(midinotes[i]); i++; while (i < len && midinotes[i].StartTime == starttime) { notegroup.Add(midinotes[i]); i++; } /* Create a single chord from the group of midi notes with * the same start time. */ ChordSymbol chord = new ChordSymbol(notegroup, key, time, clef, this); chords.Add(chord); } return chords; }
/** Return true if the chords can be connected, where their stems are * joined by a horizontal beam. In order to create the beam: * * - The chords must be in the same measure. * - The chord stems should not be a dotted duration. * - The chord stems must be the same duration, with one exception * (Dotted Eighth to Sixteenth). * - The stems must all point in the same direction (up or down). * - The chord cannot already be part of a beam. * * - 6-chord beams must be 8th notes in 3/4, 6/8, or 6/4 time * - 3-chord beams must be either triplets, or 8th notes (12/8 time signature) * - 4-chord beams are ok for 2/2, 2/4 or 4/4 time, any duration * - 4-chord beams are ok for other times if the duration is 16th * - 2-chord beams are ok for any duration * * If startQuarter is true, the first note should start on a quarter note * (only applies to 2-chord beams). */ public static bool CanCreateBeam(ChordSymbol[] chords, TimeSignature time, bool startQuarter) { int numChords = chords.Length; Stem firstStem = chords[0].Stem; Stem lastStem = chords[chords.Length-1].Stem; if (firstStem == null || lastStem == null) { return false; } int measure = chords[0].StartTime / time.Measure; NoteDuration dur = firstStem.Duration; NoteDuration dur2 = lastStem.Duration; bool dotted8_to_16 = false; if (chords.Length == 2 && dur == NoteDuration.DottedEighth && dur2 == NoteDuration.Sixteenth) { dotted8_to_16 = true; } if (dur == NoteDuration.Whole || dur == NoteDuration.Half || dur == NoteDuration.DottedHalf || dur == NoteDuration.Quarter || dur == NoteDuration.DottedQuarter || (dur == NoteDuration.DottedEighth && !dotted8_to_16)) { return false; } if (numChords == 6) { if (dur != NoteDuration.Eighth) { return false; } bool correctTime = ((time.Numerator == 3 && time.Denominator == 4) || (time.Numerator == 6 && time.Denominator == 8) || (time.Numerator == 6 && time.Denominator == 4) ); if (!correctTime) { return false; } if (time.Numerator == 6 && time.Denominator == 4) { /* first chord must start at 1st or 4th quarter note */ int beat = time.Quarter * 3; if ((chords[0].StartTime % beat) > time.Quarter/6) { return false; } } } else if (numChords == 4) { if (time.Numerator == 3 && time.Denominator == 8) { return false; } bool correctTime = (time.Numerator == 2 || time.Numerator == 4 || time.Numerator == 8); if (!correctTime && dur != NoteDuration.Sixteenth) { return false; } /* chord must start on quarter note */ int beat = time.Quarter; if (dur == NoteDuration.Eighth) { /* 8th note chord must start on 1st or 3rd quarter note */ beat = time.Quarter * 2; } else if (dur == NoteDuration.ThirtySecond) { /* 32nd note must start on an 8th beat */ beat = time.Quarter / 2; } if ((chords[0].StartTime % beat) > time.Quarter/6) { return false; } } else if (numChords == 3) { bool valid = (dur == NoteDuration.Triplet) || (dur == NoteDuration.Eighth && time.Numerator == 12 && time.Denominator == 8); if (!valid) { return false; } /* chord must start on quarter note */ int beat = time.Quarter; if (time.Numerator == 12 && time.Denominator == 8) { /* In 12/8 time, chord must start on 3*8th beat */ beat = time.Quarter/2 * 3; } if ((chords[0].StartTime % beat) > time.Quarter/6) { return false; } } else if (numChords == 2) { if (startQuarter) { int beat = time.Quarter; if ((chords[0].StartTime % beat) > time.Quarter/6) { return false; } } } foreach (ChordSymbol chord in chords) { if ((chord.StartTime / time.Measure) != measure) return false; if (chord.Stem == null) return false; if (chord.Stem.Duration != dur && !dotted8_to_16) return false; if (chord.Stem.isBeam) return false; } /* Check that all stems can point in same direction */ bool hasTwoStems = false; int direction = Stem.Up; foreach (ChordSymbol chord in chords) { if (chord.HasTwoStems) { if (hasTwoStems && chord.Stem.Direction != direction) { return false; } hasTwoStems = true; direction = chord.Stem.Direction; } } /* Get the final stem direction */ if (!hasTwoStems) { WhiteNote note1; WhiteNote note2; note1 = (firstStem.Direction == Stem.Up ? firstStem.Top : firstStem.Bottom); note2 = (lastStem.Direction == Stem.Up ? lastStem.Top : lastStem.Bottom); direction = StemDirection(note1, note2, chords[0].Clef); } /* If the notes are too far apart, don't use a beam */ if (direction == Stem.Up) { if (Math.Abs(firstStem.Top.Dist(lastStem.Top)) >= 11) { return false; } } else { if (Math.Abs(firstStem.Bottom.Dist(lastStem.Bottom)) >= 11) { return false; } } return true; }
private SheetMusic sheetmusic; /** Used to get colors and other options */ /** Create a new Chord Symbol from the given list of midi notes. * All the midi notes will have the same start time. Use the * key signature to get the white key and accidental symbol for * each note. Use the time signature to calculate the duration * of the notes. Use the clef when drawing the chord. */ public ChordSymbol(List <MidiNote> midinotes, KeySignature key, TimeSignature time, Clef c, SheetMusic sheet) { int len = midinotes.Count; int i; hastwostems = false; clef = c; sheetmusic = sheet; starttime = midinotes[0].StartTime; endtime = midinotes[0].EndTime; for (i = 0; i < midinotes.Count; i++) { if (i > 1) { if (midinotes[i].Number < midinotes[i - 1].Number) { throw new System.ArgumentException("Chord notes not in increasing order by number"); } } endtime = Math.Max(endtime, midinotes[i].EndTime); } notedata = CreateNoteData(midinotes, key, time); accidsymbols = CreateAccidSymbols(notedata, clef); /* Find out how many stems we need (1 or 2) */ NoteDuration dur1 = notedata[0].duration; NoteDuration dur2 = dur1; int change = -1; for (i = 0; i < notedata.Length; i++) { dur2 = notedata[i].duration; if (dur1 != dur2) { change = i; break; } } if (dur1 != dur2) { /* We have notes with different durations. So we will need * two stems. The first stem points down, and contains the * bottom note up to the note with the different duration. * * The second stem points up, and contains the note with the * different duration up to the top note. */ hastwostems = true; stem1 = new Stem(notedata[0].whitenote, notedata[change - 1].whitenote, dur1, Stem.Down, NotesOverlap(notedata, 0, change) ); stem2 = new Stem(notedata[change].whitenote, notedata[notedata.Length - 1].whitenote, dur2, Stem.Up, NotesOverlap(notedata, change, notedata.Length) ); } else { /* All notes have the same duration, so we only need one stem. */ int direction = StemDirection(notedata[0].whitenote, notedata[notedata.Length - 1].whitenote, clef); stem1 = new Stem(notedata[0].whitenote, notedata[notedata.Length - 1].whitenote, dur1, direction, NotesOverlap(notedata, 0, notedata.Length) ); stem2 = null; } /* For whole notes, no stem is drawn. */ if (dur1 == NoteDuration.Whole) { stem1 = null; } if (dur2 == NoteDuration.Whole) { stem2 = null; } width = MinWidth; }
/** Given the raw midi notes (the note number and duration in pulses), * calculate the following note data: * - The white key * - The accidental (if any) * - The note duration (half, quarter, eighth, etc) * - The side it should be drawn (left or side) * By default, notes are drawn on the left side. However, if two notes * overlap (like A and B) you cannot draw the next note directly above it. * Instead you must shift one of the notes to the right. * * The KeySignature is used to determine the white key and accidental. * The TimeSignature is used to determine the duration. */ private static NoteData[] CreateNoteData(List<MidiNote> midinotes, KeySignature key, TimeSignature time) { int len = midinotes.Count; NoteData[] notedata = new NoteData[len]; for (int i = 0; i < len; i++) { MidiNote midi = midinotes[i]; notedata[i] = new NoteData(); notedata[i].number = midi.Number; notedata[i].leftside = true; notedata[i].whitenote = key.GetWhiteNote(midi.Number); notedata[i].duration = time.GetNoteDuration(midi.EndTime - midi.StartTime); notedata[i].accid = key.GetAccidental(midi.Number, midi.StartTime / time.Measure); if (i > 0 && (notedata[i].whitenote.Dist(notedata[i-1].whitenote) == 1)) { /* This note (notedata[i]) overlaps with the previous note. * Change the side of this note. */ if (notedata[i-1].leftside) { notedata[i].leftside = false; } else { notedata[i].leftside = true; } } else { notedata[i].leftside = true; } } return notedata; }
public void init(MidiFile file, MidiOptions options, List <MidiTrack> tracks) { zoom = 1.0f; filename = file.FileName; SetColors(options.colors, options.shadeColor, options.shade2Color); pen = new Pen(Color.Black, 1); SetNoteSize(options.largeNoteSize); scrollVert = options.scrollVert; showNoteLetters = options.showNoteLetters; TimeSignature time = file.Time; if (options.time != null) { time = options.time; } if (options.key == -1) { mainkey = GetKeySignature(tracks); } else { mainkey = new KeySignature(options.key); } numtracks = tracks.Count; int lastStart = file.EndTime() + options.shifttime; /* Create all the music symbols (notes, rests, vertical bars, and * clef changes). The symbols variable contains a list of music * symbols for each track. The list does not include the left-side * Clef and key signature symbols. Those can only be calculated * when we create the staffs. */ List <MusicSymbol>[] symbols = new List <MusicSymbol> [numtracks]; for (int tracknum = 0; tracknum < numtracks; tracknum++) { MidiTrack track = tracks[tracknum]; ClefMeasures clefs = new ClefMeasures(track.Notes, time.Measure); List <ChordSymbol> chords = CreateChords(track.Notes, mainkey, time, clefs); symbols[tracknum] = CreateSymbols(chords, clefs, time, lastStart); } List <LyricSymbol>[] lyrics = null; if (options.showLyrics) { lyrics = GetLyrics(tracks); } /* Vertically align the music symbols */ SymbolWidths widths = new SymbolWidths(symbols, lyrics); AlignSymbols(symbols, widths, options); staffs = CreateStaffs(symbols, mainkey, options, time.Measure); CreateAllBeamedChords(symbols, time); if (lyrics != null) { AddLyricsToStaffs(staffs, lyrics); } /* After making chord pairs, the stem directions can change, * which affects the staff height. Re-calculate the staff height. */ foreach (Staff staff in staffs) { staff.CalculateHeight(); } BackColor = Color.White; SetZoom(1.0f); }