PSAM 控件库
包含用于绘制音乐音符的 IncipitViewer 控件的 WinForms 库。
引言
更新:本文介绍了一个旧的 .NET 项目,该项目最终发展成为更大的开源框架 Manufaktura.Controls。您可以在此处阅读: https://codeproject.org.cn/Articles/1252423/Music-Notation-in-NET
PSAM 控件库是一个 WinForms 库,其中包含 IncipitViewer
控件,用于绘制可从MusicXml 文件读取或以编程方式添加的音乐音符。该库最初是一个更大的软件 Polish System for Archivising Music(http://www.archiwistykamuzyczna.pl/?lang=en)的组件,但我想它可能对其他软件开发人员有用,因此我决定在 BSD 许可下分发它。PSAM 控件库是用 C# 在 Microsoft Visual Studio Express 中编写的。
以下屏幕截图说明了在手稿数据库应用程序中使用许多 IncipitViewer
控件。
PSAM 控件库的官方网站可在 http://musicengravingcontrols.com/ 找到。
Using the Code
IncipitViewer
控件需要一个特殊的字体来绘制音符和其他音乐符号。您可以创建自己的字体,或使用包含的 Polihymnia 字体,该字体基于 Ben Laenen 的 Euterpe 字体,并根据 Sil Open Font License 分发。当然,您必须将字体安装到您的字体目录中才能正确显示音符。
将 IncipitViewer
控件添加到项目的最简单方法是将 PSAMControlLibrary.dll 文件拖放到您的 Toolbox
中,然后将 IncipitViewer
控件拖放到您的窗体中。您也可以以编程方式创建 IncipitViewer
控件,例如:
IncipitViewer viewer = new IncipitViewer();
viewer.Dock = DockStyle.Fill;
Controls.Add(viewer);
请记住在代码中添加 using <code>PSAMControlLibrary;
指令。
要从MusicXml 文件读取音乐,请使用 LoadFromXmlFile(string fileName)
方法,并将要打开的 XML 文件的路径作为参数。请记住,仅支持第一个谱表 - 其他谱表将被跳过。
viewer.LoadFromXmlFile("example.xml");
上述代码的效果应如下所示:
要清除谱表,请使用 ClearMusicalIncipit()
方法。
viewer.ClearMusicalIncipit();
我们也可以以编程方式添加音符和音乐符号。首先,我们将在第二行添加一个 G 谱号。
Clef c = new Clef(ClefType.GClef, 2);
viewer.AddMusicalSymbol(c);
然后我们将添加一个 G 四分音符。
Note n = new Note("G", 0, 4, MusicalSymbolDuration.Quarter,
NoteStemDirection.Up, NoteTieType.None,
new List<NoteBeamType>() {NoteBeamType.Single});
viewer.AddMusicalSymbol(n);
Note 构造函数的第一个参数是表示音级名称的 string
:A、B、C、D、E、F、G。第二个参数是升号(正数)或降号(负数)的数量,0 表示没有变音记号。第三个参数是八度数。接下来的参数是:音符的时值、符干方向和连音线类型(如果音符未连音,则为 NoteTieType.None
)。最后一个参数是连音线列表。如果音符没有任何连音线,它仍必须有一个包含单个元素 NoteBeamType.Single
的列表(即使音符的时值大于八分音符)。为了清楚连音线列表的工作方式,让我们尝试添加一组两个连音的十六分音符和八分音符。
Note s1 = new Note("A", 0, 4, MusicalSymbolDuration.Sixteenth,
NoteStemDirection.Down, NoteTieType.None,
new List<NoteBeamType>() { NoteBeamType.Start, NoteBeamType.Start});
Note s2 = new Note("C", 1, 5, MusicalSymbolDuration.Sixteenth,
NoteStemDirection.Down, NoteTieType.None,
new List<NoteBeamType>() { NoteBeamType.Continue, NoteBeamType.End });
Note e = new Note("D", 0, 5, MusicalSymbolDuration.Eighth,
NoteStemDirection.Down, NoteTieType.None,
new List<NoteBeamType>() { NoteBeamType.End });
viewer.AddMusicalSymbol(s1);
viewer.AddMusicalSymbol(s2);
viewer.AddMusicalSymbol(e);
结果应如下所示:
当所有连音线都设置为 NoteBeamType.Single
时,上面的示例看起来是这样的。
您可以通过更改 MusicalCharacterColor
属性来绘制彩色音符和音乐符号。
虽然 IncipitViewer
不支持多个谱表,但它支持和弦,因为和弦是许多单音乐器风格的一部分。如果音符的 IsChordElement
属性设置为 true
,则该音符被视为前一个音符的和弦元素。
Note n1 = new Note("C", 0, 4, MusicalSymbolDuration.Half,
NoteStemDirection.Up, NoteTieType.None,
new List<NoteBeamType>() { NoteBeamType.Single });
Note n2 = new Note("E", 0, 4, MusicalSymbolDuration.Half,
NoteStemDirection.Up, NoteTieType.None,
new List<NoteBeamType>() { NoteBeamType.Single });
Note n3 = new Note("G", 0, 4, MusicalSymbolDuration.Half,
NoteStemDirection.Up, NoteTieType.None,
new List<NoteBeamType>() { NoteBeamType.Single });
n2.IsChordElement = true;
n3.IsChordElement = true;
viewer.AddMusicalSymbol(n1);
viewer.AddMusicalSymbol(n2);
viewer.AddMusicalSymbol(n3);
结果
以下示例显示了如何插入圆点、休止符和小节线。
Note n4 = new Note("A", 0, 4, MusicalSymbolDuration.Half,
NoteStemDirection.Up, NoteTieType.None,
new List<NoteBeamType>() { NoteBeamType.Single });
n4.NumberOfDots = 1;
Rest r = new Rest(MusicalSymbolDuration.Quarter);
Barline b = new Barline();
viewer.AddMusicalSymbol(n4);
viewer.AddMusicalSymbol(r);
viewer.AddMusicalSymbol(b);
结果
当鼠标悬停在控件上时,右上角会出现两个按钮:第一个保存控件关联的MusicXml 文件,第二个调用 OnPlayExternalMidiPlayer
事件处理程序。您可以订阅 PlayExternalMidiPlayer
事件。
viewer.PlayExternalMidiPlayer +=
new IncipitViewer.PlayExternalMidiPlayerDelegate(viewer_PlayExternalMidiPlayer);
创建以下函数来处理事件。
void viewer_PlayExternalMidiPlayer(IncipitViewer sender)
{
//Place your code here
}
在上面的函数中,您可以放置代码来读取控件中的音符,并使用您自己的函数或另一个库来播放它们。要从控件访问所需的音乐符号,请使用 IncipitViewer.IncipitElement(int i)
方法,其中 i
是元素的索引。要将音符转换为 MIDI 音高,您可以使用 MusicalSymbol.ToMidiPitch (string step, int alter, int octave)
方法。
要打印 IncipitViewer
控件的内容,请创建一个 PrintDocument
对象并订阅其 PrintPage
事件。
private void printDocument1_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
Graphics g = e.Graphics;
viewer.DrawViewer(g, true);
}
然后使用 Print()
方法打印文档。
PrintDialog dlg = new PrintDialog();
dlg.Document = printDocument1;
if (dlg.ShowDialog() == DialogResult.OK)
{
printDocument1.Print();
}
打印样本
关注点
与乐谱的图形布局相关的一个奇特问题是确定符干在连音线下的合适长度。在 17世纪和 18世纪的印刷音乐中,经常使用等长符干,其后果是“断裂”的连音线。乐谱看起来是这样的:
然而,在现代音乐制谱中,连音线必须是直的。不幸的是,直线连音线会导致符干长度可变,并且符干的合适长度必须以编程方式确定。下图展示了完成此操作所需的所有值。
要确定符干末端的位置,我们必须找出段落 y1 的长度,这是从组中最后一个音符的符干末端到我们感兴趣的音符的符干末端的垂直距离。因为
tg alpha = y<sub>1</sub>/x<sub>1</sub>
然后
y<sub>1</sub> = x<sub>1</sub> * tg alpha
tg alpha 的值是组中第一个和最后一个音符符干末端之间的垂直距离与组中第一个和最后一个音符符干末端之间的水平距离之比(我们假设我们知道这两个值)。
tg alpha = y / x
所以:
y<sub>1</sub> = x<sub>1</sub> * (y / x)
y1 是我们感兴趣的音符的符干末端的点的垂直坐标。
历史
在版本 2.1.0.2 中,我添加了彩色音符,并对 IncipitViewer
类进行了一些更改,以便将此库移植到 WPF。WPF 版本的库可在此处获得:HERE。