Click here to Skip to main content
14,265,784 members

A Flexible Direct2D Pianoroll for Your Music Apps

Rate this:
4.90 (9 votes)
Please Sign up or sign in to vote.
4.90 (9 votes)
1 May 2019CPOL
Create music easily
This is an old version of the currently published article.

Introduction

This is one of my oldest projects, now available as an open source. A piano roll is an easy way to compose music without knowledge of musical score elements.

Featuring:

  • Notes support channel (0-15), velocity (0-127), layer (unlimited)
  • Moving, resizing with snap controls
  • Supports diatonic movement through specified Key and Mode (Major/Minor)
  • Unlimited undo/redo
  • Unlimited layers
  • Piano side (Left/Right)
  • Callbacks
  • Tools - Auto, Eraser, Single click entry
  • Serialization/Deserialization to XML
  • Key per measure
  • Time signature per measure
  • MIDI export

The sample project demonstrates:

  • The control
  • Save/Load XML
  • Save MIDI
  • Play at run time through midi Out

Using Pianoroll

All you need is to include "pianoroll.hpp" to your project, then:

// 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);    
}

// Callbacks 

Callbacks

    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);

The mandatory callback is the RedrawRequest. When this function is called, call PIANOROLL::Paint(), passing your ID2D1RenderTarget so the control redraws itself.

Measures, Keys and Times

Key setting is used to allow the control to move notes within a specific scale (if you hold Shift while moving, notes are moved chromatically instead). For example, if the current key is D major and you press the minus key on a D note, this moves to C#.

Times allows a measure to have a different number of beats (default 4).

Keys and beats are a per measure setting. The interesting thing in the Key is the scale creation function which uses the known MMmMMMm or MmMMm3m formats for major/minor to create the scale based on the key:

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;
    }

Layers

Notes can belong to a certain layer (the default is layer 1). When working with layers, notes that belong to a layer different than the current layer cannot be modified except by the command that changes layers.

Notes

Notes have configurable velocity, channel and layer. Notes have a starting position (measure + beat) and a duration:

class NOTE
{
    public:

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

Drawing Technology

The control uses your own Direct2D rendering target, so you can draw it wherever you like (as a HWND or even as part of an image for a screenshot or in a printer HDC).

Learn more about Direct2D in my older article.

The control will notify you when it needs redrawing.

Zooming, Moving and Snapping

The control provides unlimited moving between measures and unlimited zoom in or zoom out. The control also allows note moving to be snapped to beats. This is configurable to allow denominations of the beat in order to move more precisely. If you want non snapping while moving, move the notes while holding Shift.

Fractions

All internal manipulations of the library are done with fractions, so no information is lost on floating point operations.

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

Serialization

The control uses my XML library to serialize. You can then reload the control as needed.

MIDI

A simple MIDI class is provided with the pianoroll that allows to write MIDI file data. MIDI writes have a variable length format:

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;
            }
        }

The pianoroll creates a multiple-track MIDI file (each layer becomes a track) at 120 BpM.

History

  • 1/5/2019: First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Chourdakis
Software Developer
Greece Greece
I'm working in C++, PHP , Java, Windows, iOS, Android and Web (HTML/Javascript/CSS).

I 've a PhD in Digital Signal Processing and Artificial Intelligence and I specialize in Pro Audio and AI applications.

My home page: https://www.turboirc.com

Comments and Discussions

Discussions on this specific version of this article. Add your comments on how to improve this article here. These comments will not be visible on the final published version of this article.
 
Questionhey this is really cool Pin
honey the codewitch1-May-19 4:03
memberhoney the codewitch1-May-19 4:03 
AnswerRe: hey this is really cool Pin
Michael Chourdakis1-May-19 4:09
mvaMichael Chourdakis1-May-19 4:09 
QuestionGreat !!! Pin
Serge Desmedt30-Apr-19 23:26
professionalSerge Desmedt30-Apr-19 23:26 
AnswerRe: Great !!! Pin
Michael Chourdakis1-May-19 4:13
mvaMichael Chourdakis1-May-19 4:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.


Article
Posted 30 Apr 2019

Stats

7.6K views
5 bookmarked