.NET中的音乐符号

介绍 本文简要介绍了库Manufaktura最重要的基础知识。控件,我最近作为开源项目发布了。这个项目是我八年前创建的另外两个项目的延续,在以下文章中描述: https://www.codeproject.com/articles/87329/psam-control-library https://www.codeproject.com/articles/89582/psam-wpf-control-library|0>>>> 这些年来,我的编程技能有了显著的提高,但Manufaktura。控件仍然使用来自这两个旧项目的一些代码。我完全改变了架构以允许跨平台实现,但你仍然可以找到一些旧的意大利面条代码,特别是在呈现策略的主体部分。 我将代码作为开源项目发布,因为作为一个纯粹的商业项目,它没有给我带来太多利润。我希望它作为开源软件会更有用,一个大型社区将很快围绕它出现。 由于附件大小的限制,本文附带的源代码只包含最重要的库。您可以在GIT存储库中找到整个代码:https://bitbucket.org/Ajcek/manufakturalibraries 它的作用是什么(针对那些不熟悉我的库的人) Manufaktura。控件是一组。net库,用c#编写,用于在桌面、web和移动应用程序中渲染音乐符号。核心库是跨平台的,所以它们可以在几乎任何。net环境中使用,比如。net Framework、。net core、UWP、Mono、Silverlight等等。有WPF, WinForms, UWP, ASP的实现。净MVC, ASP。核心网等。还有针对Silverlight和Windows 8的遗留库。 这些库的主要目的是呈现乐谱,但它们也提供了其他特性,如MusicXML解析、MIDI回放和用于数学操作的帮助方法,这些数学操作在音乐中很有用。 解决方案概述 解决方案中有两个主要的库:Manufaktura。控制和Manufaktura.Music。这些都是跨平台的库,因此它们可以在所有。net环境中使用:web、桌面和移动环境。以前,它们作为可移植的类库,目前它们的目标是。net标准1.1。 Manufaktura。音乐定义了音乐的低级概念,如音程、节奏、比例等。它独立于音乐符号所以模型是由Manufaktura定义的。音乐是不知道音符、休止符、谱号等概念的。这个库还定义了模型之间的算术运算,比如音程的调换、音高的比较等等。 Manufaktura。控件是解决方案中最大的库。定义了西方音乐符号模型、渲染器、解析器等。 建筑Manufaktura.Music Manufaktura的主要概念。音乐包括:音程、音高、步幅、比例、有节奏的时长和音阶。 音高概念用三种结构来表示: Step -定义一个可以改变(增加或减少)的尺度步长(a, B, C, D, E, F,或G)。步骤不知道确切的音高。Pitch -从Step继承。这是一个精确的八度音和精确的中音。TunedPitch -从Pitch继承。这是一个有着精确频率的音高。例如:A4在440Hz或A4在415Hz。 间隔也分为三种结构: 间隔时间-由步数定义。例如:二度、三度、八度等等。Interval -继承自DiatonicInterval。定义步数和半步数。例如:小调二度、大调二度、减三度等等。BoundInterval—从interval继承。表示从特定的音高概念开始的间隔,类似于钩状向量。例:从C到E的三大调。 比例只是分数,可以转换成两倍或分。例如:比例。Sesquialtera返回3/2的分数。 还有其他种类,如节奏和音阶。所有这些类或多或少都用于Manufaktura.Controls的低抽象模型中。 建筑Manufaktura.Controls Manufaktura。控件是解决方案中最大的库。它定义了模型、解析器(用于解析MusicXml)、呈现器和呈现策略。 模型 概述 模型包含代表不同西方音乐符号概念的类,如音符、休止符、谱号、线条等。在很大程度上,模型是基于MusicXml规范的,但它也利用了来自Manufaktura的概念。例如,音乐库中的音符由音高和节奏持续时间组成。一些类来自manufakura . controls。模型可以看作是来自Manufaktura的较低层次的抽象。比如音乐类,音高可以提升为音符,音符可以降低为音高,等等。 创建分数模型 乐谱包含所有要绘制的音乐符号。创建分数有两种方法: 手动使用api自动解析器 您可以学习基本的手工创建分数模型从这篇文章: http://musicengravingcontrols.com/en-我们/文章/显示? id = 2 员工的规则 手动创建模型与使用解析器创建分数不同。当您解析MusicXml时,解析器会自动应用MusicXml文件中包含的一些数据,比如度量中的水平音符位置、词干方向等。 如果您通过API手动添加注释和其他符号,一些属性(如词干方向)是由人员规则决定的。人员规则是从StaffRule继承的类。例如,当向五线谱插入注释时,NoteStemRule会自动确定词干的方向。当将StaffRules插入到继承自itemmanagingcollectiontitem>的集合时,将自动应用这些规则。这个类最常用的实现是管理Staff上的项的MusicalSymbolCollection。 渲染器和渲染策略 Manufaktura。控件为每个平台使用一个代码基。这是通过称为渲染器和渲染策略的抽象类实现的: 渲染器-他们定义如何绘制原始形状,如线,文本和贝塞尔曲线,但他们完全不知道音乐符号。渲染策略——将音乐转换成原始的形状,如线条、文本和贝塞尔曲线,但不知道如何绘制它们。 ScoreRendererBase类有五个主要的抽象方法: 导入 这五个方法是在派生类中实现的。例如,WPFCanvasScoreRenderer通过创建线条形状并将其放置在画布上来绘制线条: 隐藏,复制Code

public override void DrawLine(Primitives.Point startPoint, 
        Primitives.Point endPoint, Primitives.Pen pen, MusicalSymbol owner)
{
    if (!EnsureProperPage(owner)) return;
    if (Settings.RenderingMode != ScoreRenderingModes.Panorama)
    {
        startPoint = startPoint.Translate(CurrentScore.DefaultPageSettings);
        endPoint = endPoint.Translate(CurrentScore.DefaultPageSettings);
    }

    var line = new Line();
    line.Stroke = new SolidColorBrush(ConvertColor(pen.Color));
    line.X1 = startPoint.X;
    line.X2 = endPoint.X;
    line.Y1 = startPoint.Y;
    line.Y2 = endPoint.Y;
    line.StrokeThickness = pen.Thickness;
    line.Visibility = BoolToVisibility(owner.IsVisible);
    Canvas.Children.Add(line);
    OwnershipDictionary.Add(line, owner);
}

HtmlSvgScoreRenderer创建SVG标签并将其添加到表示SVG画布的XML文档中: 隐藏,收缩,复制Code

public override void DrawLine(Point startPoint, Point endPoint, Pen pen, Model.MusicalSymbol owner)
        {
            if (!EnsureProperPage(owner)) return;
            if (Settings.RenderingMode != ScoreRenderingModes.Panorama && 
                                              !TypedSettings.IgnorePageMargins)
            {
                startPoint = startPoint.Translate(CurrentScore.DefaultPageSettings);
                endPoint = endPoint.Translate(CurrentScore.DefaultPageSettings);
            }

            var element = new XElement("line",
                new XAttribute("x1", startPoint.X.ToStringInvariant()),
                new XAttribute("y1", startPoint.Y.ToStringInvariant()),
                new XAttribute("x2", endPoint.X.ToStringInvariant()),
                new XAttribute("y2", endPoint.Y.ToStringInvariant()),
                new XAttribute("style", pen.ToCss()),
                new XAttribute("id", BuildElementId(owner)));

            var playbackAttributes = BuildPlaybackAttributes(owner);
            foreach (var playbackAttr in playbackAttributes)
            {
                element.Add(new XAttribute(playbackAttr.Key, playbackAttr.Value));
            }

            if (startPoint.Y < ClippedAreaY) ClippedAreaY = startPoint.Y;
            if (endPoint.Y < ClippedAreaY) ClippedAreaY = endPoint.Y;
            if (startPoint.X > ActualWidth) ActualWidth = startPoint.X;
            if (endPoint.X > ActualWidth) ActualWidth = endPoint.X;
            if (startPoint.Y > ActualHeight) ActualHeight = startPoint.Y;
            if (endPoint.Y > ActualHeight) ActualHeight = endPoint.Y;

            Canvas.Add(element);
        }

注意,Canvas属性可以是任何对象(例如,控件、XML文档等)。画布类型作为ScoreRenderer的类型参数提供。 渲染策略来自于MusicalSymbolRenderStrategy。这是一个泛型类型——适当的呈现策略由类型参数匹配。例如,诺特恩的策略来自于音乐符号。每个渲染器的主要方法是: 隐藏,复制Code

public override void Render(Barline element, ScoreRendererBase renderer)

第一个参数是一个要绘制的元素。第二个参数是分数渲染器实例。每个平台使用不同的渲染器实现,但是渲染方法的代码独立于任何实现—它只是告诉渲染器绘制原始形状,如线、文本等,其余的由渲染器完成。 在呈现分数时,会为分数的每个元素注入不同的呈现策略,这样每个元素都会使用适当的呈现策略呈现。注意,不同的渲染器策略的构造函数采用不同的参数,例如,KeyRenderStrategy只使用IScoreService,而notenderstrategy也使用IBeamingService, IMeasurementService等。这些服务通过简单的IoC机制自动注入到每个呈现策略中。这些服务的作用是在不同的呈现策略实例之间存储共享数据,并为一些可以在不同呈现器之间重用的更复杂的计算提供帮助。最常用的服务是IScoreService,其中存储了员工的当前X职位。 这是绘制时间签名的渲染策略的一个例子: 隐藏,收缩,复制Code

/// <summary>
/// Strategy for rendering a TimeSignature.
/// </summary>
public class TimeSignatureRenderStrategy : MusicalSymbolRenderStrategy<TimeSignature>
{
    /// <summary>
    /// Initializes a new instance of TimeSignatureRenderStrategy
    /// </summary>
    /// <paramname="scoreService"></param>
    public TimeSignatureRenderStrategy(IScoreService scoreService) : base(scoreService)
    {
    }

    /// <summary>
    /// Renders time signature symbol with specific score renderer
    /// </summary>
    /// <paramname="element"></param>
    /// <paramname="renderer"></param>
    public override void Render(TimeSignature element, ScoreRendererBase renderer)
    {
        var topLinePosition = scoreService.CurrentLinePositions[0];
        if (element.Measure.Elements.FirstOrDefault() == element)
            scoreService.CursorPositionX += renderer.LinespacesToPixels(1); //Żeby był lekki 
                     //margines między kreską taktową a symbolem. 
                     //Być może ta linijka będzie do usunięcia

        if (element.SignatureType != TimeSignatureType.Numbers)
        {
            renderer.DrawCharacter(element.GetCharacter
                        (renderer.Settings.CurrentFont), MusicFontStyles.MusicFont,
                scoreService.CursorPositionX, topLinePosition + 
                        renderer.LinespacesToPixels(2), element);
            element.TextBlockLocation = new Primitives.Point
            (scoreService.CursorPositionX, topLinePosition + renderer.LinespacesToPixels(2));
        }
        else
        {
            if (renderer.IsSMuFLFont)
            {
                renderer.DrawString(SMuFLGlyphs.Instance.BuildTimeSignatureNumberFromGlyphs
                                   (element.NumberOfBeats),
                    MusicFontStyles.MusicFont, scoreService.CursorPositionX, 
                    topLinePosition + renderer.LinespacesToPixels(1), element);
                renderer.DrawString(SMuFLGlyphs.Instance.BuildTimeSignatureNumberFromGlyphs
                                   (element.TypeOfBeats),
                    MusicFontStyles.MusicFont, scoreService.CursorPositionX, 
                    topLinePosition + renderer.LinespacesToPixels(3), element);

                element.TextBlockLocation = new Primitives.Point
                (scoreService.CursorPositionX, topLinePosition + renderer.LinespacesToPixels(3));
            }
            else
            {
                renderer.DrawString(Convert.ToString(element.NumberOfBeats),
                    MusicFontStyles.TimeSignatureFont, scoreService.CursorPositionX, 
                    topLinePosition + renderer.LinespacesToPixels(2), element);
                renderer.DrawString(Convert.ToString(element.TypeOfBeats),
                    MusicFontStyles.TimeSignatureFont, scoreService.CursorPositionX, 
                    topLinePosition + renderer.LinespacesToPixels(4), element);

                element.TextBlockLocation = new Primitives.Point(scoreService.CursorPositionX, 
                topLinePosition + renderer.LinespacesToPixels(4));
            }
        }
        scoreService.CursorPositionX += 20;
    }
}

特定于平台的实现 概述 解决方案中数量最多的库是manufakura . controls的特定于平台的实现。就类的数量而言,这些库也是最小的。通常,它们包含以下项目: 针对特定平台控件(桌面和移动)或Razor扩展(web)的ScoreRenderer实现,利用特定的ScoreRenderer来绘制分数,有时还提供一些用户体验逻辑,如注释拖拽等。媒体播放器-用于分数回放。WPF和WinForms版本使用了一个共享的MIDI playbach实现,包含在manufakura . control . desktop中。 字体和字体度量 Manufaktura。控件使用两种字体: Polihymnia -由Ben Laenen创建的字体Euterpe (https://packages.debian.org/stable/fonts/fonts-oflb-euterpe)。这种字体只有来自原始Euterpe字体的字符子集,所以你应该避免使用它。SMuFL-compliant字体。您可以使用任何与SMuFL 1.2标准兼容的字体。本文解释了如何加载SMuFL字体和元数据:http://manufaktura-programow.pl/en-US/Articles/Display?id=8 如果你打算实现你自己的ScoreRenderer,有一些关于字体度量的事情你应该知道。首先,Polihymnia和SMuFL字体的设计方式是基线的位置与标尺(或场中心)上的线的位置重合。例如,如果您在第3行的Y坐标处放置一个notehead,那么notehead的中心将准确地显示在第3行上。不幸的是,特定的框架提供了两种完全不同的te方式xt定位: 文本块坐标是字体基线的坐标。HTML SVG就是这样工作的。文本块坐标是文本块边框左下角的坐标(例如:WPF中的文本块元素)。 第二个行为是不需要的,所以我们应该通过按基线位置转换文本块的位置来纠正文本位置。基线坐标可以从字体度量中读取,对于每个平台,字体度量必须以不同的方式检索。例如,WPF是这样做的: 隐藏,复制Code

var baseline = typeface.FontFamily.Baseline * textBlock.FontSize;
Canvas.SetLeft(textBlock, location.X);
Canvas.SetTop(textBlock, location.Y - baseline);

这在WinForms (System.Graphics)中是完全不同的: 隐藏,复制Code

var baselineDesignUnits = font.FontFamily.GetCellAscent(font.Style);
var baselinePixels = (baselineDesignUnits * font.Size) / font.FontFamily.GetEmHeight(font.Style);
Canvas.DrawString(text, font, new SolidBrush(ConvertColor(color)), 
                  new PointF((float)location.X - 4, (float)location.Y - baselinePixels));

在UWP应用程序中没有简单的方法来做到这一点,所以我决定让程序员手动提供基线位置。 测试应用程序和单元测试 在解决方案中,有一些测试应用程序和单元测试项目。最重要的是手工操作、控制、视觉检测。这是一个单元测试项目,它将一些预定义的分数(以MusicXml格式提供)呈现为位图。它将创建的位图与之前创建的位图进行比较,并将所有差异标记为红色,以便您可以轻松地跟踪提交之间的回归情况。 这张照片显示了样例视觉测试结果。在两个版本的代码之间做了一些slur更正。差异用红色表示: 额外的资源 你可以阅读其他关于Manufaktura的文章和教程。控制: http://musicengravingcontrols.com/en-US/Articles/Display?id=2 这是一个Bitbucket库: https://bitbucket.org/Ajcek/manufakturalibraries/src 两篇关于开始一切的老项目的文章: https://www.codeproject.com/articles/87329/psam-control-library https://www.codeproject.com/articles/89582/psam-wpf-control-library|0>>>> 仍然需要改进的领域 适当的Xamarin的实现 有一个Xamarin。表单实现与渲染器的Android。它仍然处于测试状态,我没有时间去开发它。 渲染管道 呈现机制没有针对呈现许多页面进行优化。它首先呈现所有的音符和音乐符号,然后绘制所有的线。还应该进行某种虚拟化,以便只呈现屏幕上可见的分数部分。 支持更多的音乐符号概念 一些音乐符号仍然不支持。例如,库不能渲染渐强和渐弱标记。 HTML中的客户端呈现 目前,所有的web实现(manufakura . controls)。AspNetMvc, manufakura . controls . aspnetcore)使用服务器端呈现。 我尝试使用CSHTML5 (http://cshtml5.com/)实现这个功能,但是我没有时间处理它,而且CSHTML5仍在开发中。有三种可能实现客户端呈现: 继续执行CSHTML5项目。可以使用一些基于WebAssembly的库,比如Blazor。创建一个创建Vexflow (http://www.vexflow.com/)或Verovio (http://www.verovio.org/index.xhtml)脚本的ScoreRenderer。 本文转载于:http://www.diyabc.com/frontweb/news17315.html

原文地址:https://www.cnblogs.com/Dincat/p/13494051.html