65.9K
CodeProject 正在变化。 阅读更多。
Home

适用于音乐应用程序的灵活的 Direct2D pianoroll

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2019年5月1日

CPOL

5分钟阅读

viewsIcon

25485

轻松创作音乐

引言

这是我最古老的项目之一,现在开源了。钢琴卷帘是一种无需了解乐谱元素即可轻松创作音乐的方式。

特色

  • 音符支持通道 (0-15),力度 (0-127),层 (无限)
  • 带有吸附控制的移动、缩放
  • 支持通过指定的调式和调性 (大调/小调) 进行音阶内移动
  • 无限撤销/重做
  • 无限层
  • 钢琴侧边 (左/右/下/无)
  • 回调
  • 工具 - 自动、橡皮擦、单击输入、量化器
  • 序列化/反序列化到 XML
  • 每小节一个调
  • 每小节一个速度
  • 每小节一个拍号
  • MIDI 导出
  • 部分 MIDI 导入 (进行中)
  • 非音符 MIDI 音符
  • 元事件 (原始十六进制和特定项目)
  • 触后事件
  • 音高偏移事件
  • 音符量化
  • 标记
  • 长文本显示
  • 半音/音阶转位
  • 从 MIDI 输入设备录制
  • 流式回调
  • 可配置颜色
  • 乐段模式

 

示例项目演示

  • 控件
  • 保存/加载 XML
  • 保存 MIDI
  • 运行时通过 MIDI 输出播放
  • 从 MIDI 输入录制

您还可以查看我在 VSTX 中的钢琴卷帘示例,了解一个使用此钢琴卷帘通过 VST 乐器创作音乐的工具。

使用钢琴卷帘

您只需要将 "pianoroll.hpp" 包含到您的项目中,然后

// Instantiate
PR::PIANOROLL prx;

// Pass messages WM_KEYDOWN, WM_LBUTTONDBLCLK, WM_LBUTTONDOWN, 
// WM_RBUTTONDOWN, WM_MOUSEMOVE,WM_LBUTTONUP, WM_SYSKEYDOWN 
switch(uMessage) 
{
    case WM_KEYDOWN:
       prx.Message(uMessage,wParam,lParam);    
}

 

回调

    class PIANOROLLCALLBACK
    {
    public:

        virtual HRESULT NoteAdded(PIANOROLL* pr, NOTE*) = 0;
        virtual HRESULT NoteRemoved(PIANOROLL* pr, NOTE*) = 0;
        virtual void RedrawRequest(PIANOROLL* pr, unsigned long long param) = 0;
        virtual HRESULT OnNoteChange(PIANOROLL* pr, NOTE* oldn, NOTE* newn) = 0;
        virtual HRESULT OnNoteSelect(PIANOROLL* pr, NOTE* oldn, bool) = 0;
        virtual void OnPianoOn(PIANOROLL*, int n) = 0;
        virtual void OnPianoOff(PIANOROLL*, int off) = 0;
    };

prx.AddCallback(myPrc);

必不可少的回调是 `RedrawRequest`。当调用此函数时,调用 `PIANOROLL::Paint()`,传入您的 `ID2D1RenderTarget`,以便控件重绘自身。

小节、调和拍子

调设置用于允许控件在特定音阶内移动音符 (如果您按住 **Shift** 移动,音符将以半音方式移动)。例如,如果当前调是 D 大调,您在 D 音符上按下减号键,它将移动到 C#。

拍子允许一个乐节有不同数量的拍 (默认为 4)。

调和拍子是每小节的设置。调中最有趣的是音阶创建函数,它使用已知的 `MMmMMMm` 或 `MmMMm3m` 格式来根据调创建大调/小调音阶。

void CreateScale()
    {
        Scale.clear();
        unsigned int fi = 0x48; // C
        if (k > 0)
            fi = (7 * k) % 12;

        if (m == 1)
            fi -= 3;

        fi = fi % 12;
        if (m == 1)
        {
            Scale.push_back(fi);
            Scale.push_back(fi + 2);
            Scale.push_back(fi + 3);
            Scale.push_back(fi + 5);
            Scale.push_back(fi + 7);
            Scale.push_back(fi + 8);
            Scale.push_back(fi + 11);
        }
        else
            if (m == 0)
            {
                Scale.push_back(fi);
                Scale.push_back(fi + 2);
                Scale.push_back(fi + 4);
                Scale.push_back(fi + 5);
                Scale.push_back(fi + 7);
                Scale.push_back(fi + 9);
                Scale.push_back(fi + 11);

            }
        for (auto& e : Scale)
            e = e % 12;
    }

图层

音符可以属于某个层 (默认为层 1)。在处理层时,不属于当前层的音符不能被修改,除非通过更改层的命令。

注释

音符具有可配置的力度、通道和层。音符具有起始位置 (小节 + 拍) 和持续时间。

class NOTE
{
    public:

        int midi = 0;
        int Selected = 0;
        POSITION p;
        FRACTION d;
        int vel = 127;
        int ch = 0;
        int layer = 0;
}

此外,音符可以包含非音符事件,如乐器音色、音高弯音、系统独占事件或控制器消息。当使用不包含音符信息的事件时,您可以将它们放在任何位置 (使用 Ctrl+双击)。

 

钢琴

控件创建一个简单的钢琴,允许您测试音符。钢琴可以放在侧边 (左/右) 或底部。控件还提供纯钢琴绘图 (在我的 VSTX 库中用于 VST 乐器测试)。

绘图技术

控件使用您自己的 Direct2D 渲染目标,因此您可以随意绘制它 (作为 `HWND`,甚至作为屏幕截图的一部分,或在打印机的 `HDC` 中)。

在我的旧文**文章**中了解更多关于 `Direct2D` 的信息。

当控件需要重绘时,它会通知您。

缩放、移动、吸附和滚动

控件提供在小节之间无限移动和无限缩放的能力。控件还允许将音符移动吸附到拍子上。这可以配置为允许使用拍子的分数来更精确地移动。如果您想在移动时不吸附,请按住 **Shift** 移动音符。您可以使用箭头键或顶部的滚动条滚动。

流式传输

控件可以输出一个流式音符向量 (具有绝对时间格式),供您的 MIDI 序列器使用。

分数

所有内部库操作都使用分数完成,因此在浮点运算中不会丢失任何信息。

class FRACTION
{
    public:
        ssize_t n = 0;
        ssize_t d = 1;
    
     ... operators to multiply, add, compare etc
}

转位

半音转位很简单,我们给音符加一或减一 MIDI 音符。音阶转位更复杂,我们需要考虑当前的调和调式,然后检查音符是否属于新的音阶。

 

序列化

控件使用我的 XML 库进行序列化。然后您可以根据需要重新加载控件。

MIDI

`pianoroll` 提供了一个简单的 MIDI 类,可以写入 MIDI 文件数据。MIDI 写入采用可变长度格式。

void WriteVarLen(long value, vector<unsigned char>& b)
        {
            unsigned long long buffer = value & 0x7f;
            while ((value >>= 7) > 0)
            {
                buffer <<= 8;
                buffer |= 0x80;
                buffer += (value & 0x7f);
            }

            for (;;)
            {
                b.push_back((char)buffer);
                if (buffer & 0x80)
                    buffer >>= 8;
                else
                    break;
            }
        }

`pianoroll` 创建一个多轨 MIDI 文件 (每个层变成一个轨道)。调、文本、标记、拍号和速度变化都包含在文件中。

键盘

 

  • A : 自动工具
  • E : 橡皮擦工具
  • I : 单击输入工具
  • Q : 量化工具
  • 1,2,3,4 (上一行) : 选择下一个音符的拍子持续时间
  • Shift+1,2,3,4 (上一行) : 拍子持续时间 1/8, 1/16, 1/32, 1/64
  • < 和 > : 更改所选项目的力度
  • Ctrl+ < 和 > : 力度关闭/全开
  • < 和 > (音高偏移时): 上/下音高偏移 (与 Ctrl/Shift/Alt 组合)
  • +/- : 按音阶更改所选项目的音符位置
  • Shift +/- : 按半音更改所选项目的音符位置
  • 小键盘 +/-/* : 放大、缩小、全部显示
  • Shift + 向上/向下箭头 : 更改通道
  • Alt + 向上/向下箭头 : 更改层
  • / 和 \ : 放大/缩小音符
  • D,H,': 加倍/减半/+1/2 音符
  • Ctrl+Q : 量化音符
  • Ctrl+G : 转到小节
  • J : 连接音符
  • Ctrl+1...6 : 吸附分辨率
  • Alt+1-9 小键盘 : 切换层
  • 1-9 小键盘 : 下一个层
  • Ctrl+A : 全选
  • Ctrl+C : 复制
  • Ctrl+T : 音阶转位
  • Ctrl+Shift+T : 半音转位
  • Ctrl+V : 粘贴到最后点击的小节
  • Ctrl+Z : 撤销
  • Ctrl+Y : 重做
  • [,] : 下一个/上一个标记
  • 右/左箭头 : 移动钢琴卷帘
  • Del : 删除所选音符
  • Ctrl+Home : 滚动到开始
  • X,Z : 向左、向右移动
  • P : 切换乐段模式
  • Alt+P : 下一个乐段

鼠标

  • 双击 : 插入音符 (自动工具)
  • Ctrl + 双击 : 插入非音符事件
  • Ctrl + Shift + 双击 : 插入触后事件
  • 双击音符 : 移除音符
  • 单击音符 : 选择/取消选择 (量化工具中的量化)
  • 拖动音符外部: 选择 (插入工具中的插入,橡皮擦工具中的删除,量化工具中的量化)
  • 拖动/调整音符大小

历史

  • 2019/9/5: 更多控件,量化 
  • 2019/5/5: 键盘快捷键,标记,音高弯音,触后,音色和其他新功能。
  • 2019/5/1: 首次发布
© . All rights reserved.