using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ExtensionMethods
{
public static class IEnumerableExtensions
{
public static Int32 IndexOf<T>(this IEnumerable<T> instance, T seed) where T : IEquatable<T>
{
Int32 i = 0;
foreach (T element in instance)
{
if (element.Equals(seed))
return i;
i++;
}
return -1;
}
}
}
namespace Fret
{
using ExtensionMethods;
/// <summary>
/// Indica il nome della nota, secondo lo standard inglese.
/// </summary>
public enum NoteName
{
C = 0,
D = 2,
E = 4,
F = 5,
G = 7,
A = 9,
B = 11
}
/// <summary>
/// Rappresenta una frazione. Non espone operatori.
/// </summary>
public struct Fraction
{
public Int32 Numerator;
public Int32 Denumerator;
public Fraction(Int32 num, Int32 denum)
{
this.Numerator = num;
this.Denumerator = denum;
}
}
/// <summary>
/// Classi che implementano questa interfaccia rappresentano elementi di un file di notazione in formato GUIDO standard
/// e sono perciò rappresentabili con una stringa opportunamente formattata.
/// </summary>
public interface IGuidoFormattable
{
/// <summary>
/// Restituisce la rappresentazione di questa entità nel formato di notazione GUIDO.
/// </summary>
String ToGuidoNotation();
}
public interface ITypedCloneable<T>
{
T Clone();
}
public interface IGuidoElement: IGuidoFormattable, ITypedCloneable<IGuidoElement>
{ }
/// <summary>
/// Indica una nota sulla tastiera. Istanze di quela classe sono oggetti immutabili.
/// </summary>
public class NotePitch
{
private Int32 noteIndex = 0;
private static Int32[] accidentalsIndex = { 1, 3, 6, 8, 10 }; // indici delle note alterate
private static Int32 minNoteIndex = 0; // inizia dal do della terza ottava sotto la centrale (e non dal la come nel pianoforte)
private static Int32 maxNoteIndex = 84;
// Segue il pool-pattern. Dato che ogni istanza è immutabile, è inutile creare più istanze uguali
private static Dictionary
<Int32, NotePitch
> notesPool
= new Dictionary
<Int32, NotePitch
>();
/// <summary>
/// Indica il nome della nota, scondo lo standard inglese.
/// </summary>
public NoteName Name { get; private set; }
/// <summary>
/// Indica l'ottava in cui si trova la nota. L'ottava centrale ha valore 1.
/// </summary>
public SByte Octave { get; private set; }
/// <summary>
/// Determina le alterazioni applicate alla nota. +1 indica un diesis, -1 un bemolle (analogamente +2 e -2).
/// 0 Indica che non è presente nessuna alterazione.
/// </summary>
public SByte Accidentals { get; private set; }
private NotePitch() { }
public static NotePitch CreateFromName(NoteName name, SByte accidentals = 0, SByte octave = 1)
{
if ((octave < -3) || (octave > 3))
throw new ArgumentOutOfRangeException
("octave deve essere compreso tra -3 e 3.");
Int32 noteIndex = (octave + 3) * 12 + (Int32)name + accidentals;
if (notesPool.ContainsKey(noteIndex))
return notesPool[noteIndex];
NotePitch result
= new NotePitch
();
result.Name = name;
result.Accidentals = accidentals;
result.Octave = octave;
result.noteIndex = noteIndex;
return result;
}
public static NotePitch CreateFromKeyboardIndex(Int32 noteIndex, SByte defaultAccidentals = +1)
{
if ((noteIndex < minNoteIndex) || (noteIndex > maxNoteIndex))
throw new IndexOutOfRangeException
(String.
Format("noteIndex deve essere compreso tra {0} e {1}.", minNoteIndex, maxNoteIndex
));
if (notesPool.ContainsKey(noteIndex))
return notesPool[noteIndex];
NotePitch result
= new NotePitch
();
Int32 noteIndexWithinOctave = noteIndex % 12;
result.noteIndex = noteIndex;
result.Octave = (SByte)((noteIndex / 12) - 3);
if (accidentalsIndex.IndexOf(noteIndexWithinOctave) > -1)
{
if (defaultAccidentals > 0)
{
result.Name = (NoteName)(noteIndexWithinOctave - 1);
result.Accidentals = +1;
}
else if (defaultAccidentals < 0)
{
result.Name = (NoteName)(noteIndexWithinOctave + 1);
result.Accidentals = -1;
}
}
else
result.Name = (NoteName)noteIndexWithinOctave;
return result;
}
public NotePitch Transpose(Int32 offset, SByte defaultAccidentals)
{
if (defaultAccidentals == 0)
defaultAccidentals = (SByte)Math.Sign(offset);
return NotePitch.CreateFromKeyboardIndex(this.noteIndex + offset, defaultAccidentals);
}
public NotePitch TransposeByOctave(SByte offset)
{
return this.Transpose(12 * offset, this.Accidentals);
}
public NotePitch TransposeByInterval(Key key, Int32 interval)
{
return key.GetDiatonicHarmonic(this, interval);
}
public Int32 GetDistance(NotePitch otherPitch)
{
return this.noteIndex - otherPitch.noteIndex;
}
public override String ToString()
{
return String.Format("{0}{1}{2}",
Enum.
GetName(typeof(NoteName
),
this.
Name).
ToLower(),
(this.Accidentals != 0 ?
(this.
Accidentals > 0
? new String('#',
this.
Accidentals)
: new String('&',
-this.
Accidentals))
: ""),
this.Octave);
}
}
/// <summary>
/// Rappresenta una nota musicale all'interno di un pezzo.
/// </summary>
public class Note : IGuidoElement
{
public static Type Type
= typeof(Note
);
private static Type noteNameType
= typeof(NoteName
);
private static Regex guidoNoteRegex
= new Regex
("(?<Name>[a-g])(?<Acc>(#|&)+)?(?<Octave>(\\-)?\\d)\\*(?<Num>\\d+)/(?<Den>\\d+)(?<Dots>\\.+)?", RegexOptions.
IgnoreCase);
public NotePitch Pitch { get; set; }
/// <summary>
/// Contiene la durata della nota, sottoforma di frazione.
/// </summary>
public Fraction Duration { get; set; }
/// <summary>
/// Specifica il numero di punti usati per modificare la durata della nota.
/// </summary>
public Byte NumberOfDots { get; set; }
private Note()
{
this.
Duration = new Fraction
(1, 1
);
this.NumberOfDots = 0;
}
public Note(NotePitch pitch, Fraction duration, Byte numberOfDots)
{
this.Pitch = pitch;
this.Duration = duration;
this.NumberOfDots = numberOfDots;
}
public String ToGuidoNotation()
{
return String.Format("{0}*{1}/{2}{3}",
this.Pitch.ToString(),
this.Duration.Numerator,
this.Duration.Denumerator,
(this.
NumberOfDots > 0
? new String('.',
this.
NumberOfDots) : ""));
}
/// <summary>
/// Crea un oggetto Note a partire dalla sua rappresentazione in notazione GUIDO.
/// </summary>
/// <param name="noteString">Un'opportuna stringa contenente la rappresentazione della nota.</param>
public static Note FromGuidoNotation(String noteString)
{
Note result = null;
Match m = guidoNoteRegex.Match(noteString);
if (m.Success)
{
NoteName name;
SByte accidentals = 0, octave;
name = (NoteName)Enum.Parse(noteNameType, m.Groups["Name"].Value.ToUpper());
if (m.Groups["Acc"] != null)
if (m.Groups["Acc"].Value.Contains("#"))
accidentals = (SByte)m.Groups["Acc"].Length;
else
accidentals = (SByte)(-m.Groups["Acc"].Length);
octave = Convert.ToSByte(m.Groups["Octave"].Value);
result.Pitch = NotePitch.CreateFromName(name, accidentals, octave);
result.
Duration = new Fraction
(Convert.
ToInt32(m.
Groups["Num"].
Value), Convert.
ToInt32(m.
Groups["Den"].
Value));
if (m.Groups["Dots"] != null)
result.NumberOfDots = (Byte)m.Groups["Dots"].Length;
}
else
throw new FormatException
("La stringa '" + noteString
+ "' non è conforme alla notazione GUIDO standard.");
return result;
}
public Note TypedClone()
{
return (this.Clone() as Note);
}
public IGuidoElement Clone()
{
return this.MemberwiseClone() as IGuidoElement;
}
/// <summary>
/// Restituisce una nota che si trova a offset semitoni di distanza da quella corrente.
/// </summary>
/// <param name="offset">Il numero di semitoni di cui transporre la nota.</param>
/// <param name="defaultAccidental">Se la nota a cui si arriva dopo la trasposizione non è una nota "naturale", ossia senza alterazioni, le sarà associato di default un diesis se
/// la trasposizione era a salire, o un bemolle se era a scendere. Questo parametro modifica il comportamento predefinito, in modo che la nota risultante venga alterata solo con diesis
/// nel caso defaultAccidental sia positivo, o con un bemolle nel caso sia negativo.</param>
public Note CloneAndTranspose(Int32 offset, SByte defaultAccidental = 0)
{
Note result = this.Clone() as Note;
result.Pitch = result.Pitch.Transpose(offset, defaultAccidental);
return result;
}
public Note CloneAndTrasposeByInterval(Key key, Int32 interval)
{
Note result = this.TypedClone();
result.Pitch = key.GetDiatonicHarmonic(this.Pitch, interval);
return result;
}
}
/// <summary>
/// Rappresenta una pausa.
/// </summary>
public class Pause : IGuidoElement
{
public static Type Type
= typeof(Pause
);
private static Regex guidoPauseRegex
= new Regex
("_\\*(?<Num>\\d+)/(?<Den>\\d+)(?<Dots>\\.+)?", RegexOptions.
IgnoreCase);
/// <summary>
/// Indica la durata della pausa, sottoforma di frazione.
/// </summary>
public Fraction Duration { get; set; }
/// <summary>
/// Specifica il numero di punti usati per modificare la durata della pausa.
/// </summary>
public Byte NumberOfDots { get; set; }
public String ToGuidoNotation()
{
return String.Format("_*{0}/{1}{2}",
this.Duration.Numerator,
this.Duration.Denumerator,
(this.
NumberOfDots > 0
? new String('.',
this.
NumberOfDots) : ""));
}
/// <summary>
/// Crea un oggetto Pause a partire dalla sua rappresentazione in notazione GUIDO.
/// </summary>
/// <param name="pauseString">Un'opportuna stringa contenente la rappresentazione della pause.</param>
public static Pause FromGuidoNotation(String pauseString)
{
Pause result = null;
Match m = guidoPauseRegex.Match(pauseString);
if (m.Success)
{
result.
Duration = new Fraction
(Convert.
ToInt32(m.
Groups["Num"].
Value), Convert.
ToInt32(m.
Groups["Den"].
Value));
if (m.Groups["Dots"] != null)
result.NumberOfDots = (Byte)m.Groups["Dots"].Length;
}
else
throw new FormatException
("La stringa '" + pauseString
+ "' non è conforme alla notazione GUIDO standard.");
return result;
}
public IGuidoElement Clone()
{
return this.MemberwiseClone() as IGuidoElement;
}
}
/// <summary>
/// Rappresenta un accordo. All'atto pratico è una lista di oggetti Note.
/// </summary>
public class Chord : List<Note>, IGuidoElement
{
public static Type Type
= typeof(Chord
);
public String ToGuidoNotation()
{
return "{" + this.Select(note => note.ToGuidoNotation())
.Aggregate("", (partial, str) => partial + (partial.Length > 0 ? ", " : "") + str) + "}";
}
public IGuidoElement Clone()
{
Chord result
= new Chord
();
foreach (Note note in this)
result.Add(note.Clone() as Note);
return result as IGuidoElement;
}
}
/// <summary>
/// Rappresenta la tonalità di un brano.
/// </summary>
public class Key : IGuidoElement
{
public static Type Type
= typeof(Key
);
private static NoteName[] flatsOrder = { NoteName.B, NoteName.E, NoteName.A, NoteName.D, NoteName.G, NoteName.C, NoteName.F };
private static NoteName[] sharpsOrder = { NoteName.F, NoteName.C, NoteName.G, NoteName.D, NoteName.A, NoteName.E, NoteName.B };
private static Byte[] majorKeyIntervals = { 2, 2, 1, 2, 2, 2 }; // semintoni tra una nota e l'altra della scala maggiore
private static Byte[] minorKeyIntervals = { 2, 1, 2, 2, 1, 3 }; // semitoni tra una nota e l'altra della scala minore armonica
private SByte accidentals;
private Boolean isMajor;
private NotePitch[] keyNotes;
/// <summary>
/// Indica il numero di alterazioni presenti nell'armatura in chiave. Un valore positivo indica il numero di diesis,
/// mentre uno negativo il numero di bemolli.
/// </summary>
public SByte Accidentals
{
get { return accidentals; }
set
{
accidentals = value;
if (isMajor)
keyNotes = this.GetMajorKeyNotes();
else
keyNotes = this.GetMinorKeyNotes();
}
}
/// <summary>
/// Indica se si tratta di una tonalità maggiore o minore.
/// </summary>
public Boolean IsMajor
{
get { return isMajor; }
set
{
isMajor = value;
if (isMajor)
keyNotes = this.GetMajorKeyNotes();
else
keyNotes = this.GetMinorKeyNotes();
}
}
/// <summary>
/// Crea un nuovo oggetto per rappresentare una tonalità .
/// </summary>
/// <param name="accidentals">Indica il numero di alterazioni presenti nell'armatura in chiave. Un valore positivo indica il numero di diesis,
/// mentre uno negativo il numero di bemolli.</param>
/// <param name="isMajor">Indica se si tratta di una tonalità maggiore o minore.</param>
public Key(SByte accidentals, Boolean isMajor)
{
this.accidentals = accidentals;
this.IsMajor = IsMajor;
}
/// <summary>
/// Restituisce le prime 7 note (a partire dall'ottava centrale) della scala maggiore o minore armonica.
/// </summary>
public NotePitch[] KeyNotes { get { return keyNotes; } }
public String ToGuidoNotation()
{
return String.Format("\\key<{0}>", this.Accidentals);
}
/// <summary>
/// Restituisce la nota che forma un intervallo diatonico maggiore, minore o giusto (a seconda della tonalità ) con la nota specificata, purché tale nota compaia nella tonalità corrente.
/// Ad esempio, se la tonalità è Do maggiore, baseNote è un Re e interval vale 3, restituirà un Fa. Se interval vale -3 restituirà un Si dell'ottava inferiore.
/// Se la tonalità è Re minore e baseNote è un La, se interval vale 1, restituirà Sib.
/// </summary>
/// <param name="baseNote">La nota di base dell'intervallo.</param>
/// <param name="interval">L'intervallo da formare.</param>
/// <returns></returns>
public NotePitch GetDiatonicHarmonic(NotePitch baseNote, Int32 interval)
{
if (Math.Abs(interval) <= 1)
return baseNote;
for (Int32 i = 0; i < 7; i++)
if (baseNote.Name == this.KeyNotes[i].Name)
{
Int32 newNoteIndex = i + interval - 1 * Math.Sign(interval);
Int32 index = newNoteIndex;
NoteName name;
SByte accidentals, octave;
octave = baseNote.Octave;
if (newNoteIndex >= 7)
index = newNoteIndex % 7;
else if (newNoteIndex < 0)
index = (7 - Math.Abs(newNoteIndex % 7)) % 7;
name = this.KeyNotes[index].Name;
accidentals = this.keyNotes[index].Accidentals;
octave += (SByte)(interval / 8);
if (name < baseNote.Name && interval > 0)
octave++;
else if (name > baseNote.Name && interval < 0)
octave--;
return NotePitch.CreateFromName(name, accidentals, octave);
}
return null;
}
public IGuidoElement Clone()
{
return this.MemberwiseClone() as IGuidoElement;
}
private NotePitch GetTonicKeyNote()
{
NotePitch keyNote;
if (this.Accidentals > 0)
keyNote = NotePitch.CreateFromName(sharpsOrder[this.Accidentals - 1]).Transpose(+2, 1); // tonica: x# + 1 semitono
else if (this.Accidentals < 0)
{
Int32 index = -this.Accidentals - 2; // tonica: il bemolle precedente a xb nella scala
SByte acc = -1;
if (index < 0) // significa che la tonalità è Fa
{
index += 7;
acc = 0;
}
keyNote = NotePitch.CreateFromName(flatsOrder[index], acc);
}
else
keyNote = NotePitch.CreateFromName(NoteName.C);
return keyNote;
}
private NotePitch[] GetMajorKeyNotes()
{
NotePitch
[] result
= new NotePitch
[7
];
NotePitch keyNote;
keyNote = this.GetTonicKeyNote();
result[0] = keyNote;
for(Int32 i = 0; i < majorKeyIntervals.Length; i++)
{
keyNote = keyNote.Transpose(majorKeyIntervals[i], this.Accidentals);
result[i + 1] = keyNote;
}
return result;
}
private NotePitch[] GetMinorKeyNotes()
{
NotePitch
[] result
= new NotePitch
[7
];
NotePitch keyNote;
keyNote = this.GetTonicKeyNote();
keyNote = keyNote.Transpose(-3, this.Accidentals);
result[0] = keyNote;
for (Int32 i = 0; i < minorKeyIntervals.Length; i++)
{
//
keyNote = keyNote.Transpose(minorKeyIntervals[i], (SByte)(i < minorKeyIntervals.Length - 1 ? this.Accidentals : +1));
result[i + 1] = keyNote;
}
return result;
}
}
/// <summary>
/// Rappresenta l'indicazione di tempo di un pezzo.
/// </summary>
public class Tempo : IGuidoElement
{
public static Type Type
= typeof(Tempo
);
/// <summary>
/// Indica quanto vale la figura musiclae che si pone come unità (battito).
/// </summary>
public Fraction BeatDuration { get; set; }
/// <summary>
/// Indica il numero di battiti per minuto.
/// </summary>
public Int32 BeatsPerMinute { get; set; }
public Tempo(Fraction beatDuration, Int32 beatsPerMinute)
{
this.BeatDuration = beatDuration;
this.BeatsPerMinute = beatsPerMinute;
}
public String ToGuidoNotation()
{
return String.Format("\\tempo<\"\",\"{0}/{1}={2}\">", this.BeatDuration.Numerator, this.BeatDuration.Denumerator, this.BeatsPerMinute);
}
public IGuidoElement Clone()
{
return this.MemberwiseClone() as IGuidoElement;
}
}
/// <summary>
/// Rappresenta l'indicazione metrica che stabilisce la dimensione delle misure.
/// </summary>
public class Meter : IGuidoElement
{
public static Type Type
= typeof(Meter
);
/// <summary>
/// La frazione che indica la metrica da adottare (es. 4/4, 3/8, 7/16).
/// </summary>
public Fraction TimeSignature { get; set; }
public Meter(Int32 num, Int32 denum)
{
this.
TimeSignature = new Fraction
(num, denum
);
}
public String ToGuidoNotation()
{
return String.Format("\\meter<\"{0}/{1}\">", this.TimeSignature.Numerator, this.TimeSignature.Denumerator);
}
public IGuidoElement Clone()
{
return this.MemberwiseClone() as IGuidoElement;
}
}
/// <summary>
/// Rappresenta un pezzo del fregio musicale, ossia una frase musicale con propria tonalità , tempo e metrica.
/// </summary>
public class FretPiece : List<IGuidoElement>
{
private static Char[] validChars
= new Char[] { 'a',
'b',
'c',
'd',
'e',
'f',
'g',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'0',
'#',
'&',
'*',
'_',
'.',
'/' };
/// <summary>
/// Indica la metrica della frase.
/// </summary>
public Meter Meter { get; set; }
/// <summary>
/// Indica la tonalità della frase.
/// </summary>
public Key Key { get; set; }
/// <summary>
/// Indica il tempo adottato per la frase.
/// </summary>
public Tempo Tempo { get; set; }
public FretPiece()
{
this.
Key = new Key
(0,
true);
this.
Tempo = new Tempo
(new Fraction
(1, 4
), 120
);
}
/// <summary>
/// Traduce tutte le informazioni di questa frase musicale in una stringa di notazione secondo lo standard GUIDO.
/// </summary>
/// <param name="onlyNotes">Se true, le parentesi graffe e quadre all'inizio e alla fine della stringa vengono
/// omesse. Serve per unire più frasi musicali in un unico pezzo senza staccarle sintatticamente. </param>
/// <param name="includeSignature">Se true, include nella stringa anche le sequenze di tempo, metrica e tonalità .</param>
/// <returns></returns>
public String ToGuidoNotation(Boolean onlyNotes = false, Boolean includeSignature = true)
{
StringBuilder result
= new StringBuilder
();
if (!onlyNotes)
result.Append("{[");
if (includeSignature)
{
result.AppendFormat(" {0} ", this.Key.ToGuidoNotation());
result.AppendFormat(" {0} ", this.Tempo.ToGuidoNotation());
result.AppendFormat(" {0} ", this.Meter.ToGuidoNotation());
result.AppendLine();
}
foreach (IGuidoFormattable element in this)
result.AppendFormat(" {0} ", element.ToGuidoNotation());
if (!onlyNotes)
result.Append("]}");
return result.ToString();
}
/// <summary>
/// Salva questa frase come un sinolo brano in un file di notazione specificato.
/// </summary>
/// <param name="path">Il path di un file *.gmn in cui salvare la stringa.</param>
public void SaveGuidoNotationFile(String path)
{
System.IO.File.WriteAllText(path, this.ToGuidoNotation());
}
public void AddFromGuidoString(String gmnString)
{
StringBuilder buffer
= new StringBuilder
();
Boolean chordOpen = false;
Chord tmp = null;
Int32 startCount = this.Count;
gmnString += ' ';
foreach (Char c in gmnString)
{
if (validChars.Contains(c))
buffer.Append(c);
else if (c == '{')
chordOpen = true;
else if (c == '}')
{
if (tmp != null)
this.Add(tmp);
chordOpen = false;
}
else if (buffer.Length > 0)
{
String strBuffer = buffer.ToString();
if (strBuffer.Contains('_'))
{
try
{
Pause pause = Pause.FromGuidoNotation(strBuffer);
this.Add(pause);
}
catch (Exception ex)
{
this.RemoveRange(startCount, this.Count - startCount);
throw ex;
}
}
else
{
try
{
Note note = Note.FromGuidoNotation(strBuffer);
if (chordOpen)
{
if (tmp == null)
tmp.Add(note);
}
else
this.Add(note);
}
catch (Exception ex)
{
this.RemoveRange(startCount, this.Count - startCount);
throw ex;
}
}
buffer.Remove(0, buffer.Length);
}
}
buffer = null;
}
public FretPiece TypedClone()
{
FretPiece result
= new FretPiece
();
result.Meter = this.Meter.Clone() as Meter;
result.Key = this.Key.Clone() as Key;
result.Tempo = this.Tempo.Clone() as Tempo;
foreach (IGuidoElement element in this)
result.Add(element.Clone());
return result;
}
}
/// <summary>
/// Rappresenta un fregio musicale completo, come insieme di oggetti FretPiece.
/// </summary>
public class Fret : List<FretPiece>
{
/// <summary>
/// Salva tutto il brano in un dato percorso.
/// </summary>
/// <param name="path">Il path di un file *.gmn in cui salvare la stringa.</param>
public void SaveGuidoNotationFile(String path)
{
System.IO.
StreamWriter writer
= new System.IO.
StreamWriter(path
);
Boolean first = true;
writer.Write("{[");
foreach (FretPiece piece in this)
{
writer.WriteLine(piece.ToGuidoNotation(true, first));
first = false;
}
writer.Write("]}");
writer.Close();
}
}
}