melanchall / drywetmidi

.NET library to read, write, process MIDI files and to work with MIDI devices
https://melanchall.github.io/drywetmidi
MIT License
540 stars 75 forks source link

Remove note from pattern #74

Closed mo-krautsalat closed 4 years ago

mo-krautsalat commented 4 years ago

Hello, is there a way to remove a note from a pattern? Looked at the Docs and API but didn't find a solution.

Other than that I really enjoy working with your library.

Thanks in advance, Mo

melanchall commented 4 years ago

Hi,

Thanks for using the library!

Initially pattern API was designed as "construction-only". Can you clarify your case. Do you create a pattern and then want remove a note from it?

mo-krautsalat commented 4 years ago

I have a project where the user can press buttons on a GUI to create something like a pattern. In the best-case it should work like a step sequencer. My pattern is one bar long with 16 beats in it. If the user presses a button, a note should be added to the pattern and the button toggles it state to "active". If the user presses an already "active" button again, the note related to the button should be removed from the pattern.

I also tried out the tracks API but couldn't mange to get it work properly. Maybe I should have a look at it again.

Thanks for the quick answer!

melanchall commented 4 years ago

OK, I got it. I'll try to provide corresponding API as soon as possible. I suppose I'll implement it tomorrow (15 Feb).

melanchall commented 4 years ago

I've added SetNotesState extension method for Pattern:

void SetNotesState(this Pattern pattern, NoteSelection noteSelection, PatternActionState state, bool recursive = true)

noteSelection defines predicate to select notes to set state for. All required info about note (including note index) is passed. state can be one of the following values:

Name Description
Enabled Note will be exported
Disabled Note will not be exported; rest of the same length will be exported
Excluded Note will not be exported at all

In your case I suppose you should select note by its index and use Enabled/Disabled state.

New API is in develop branch so if you want to try it right now please download sources and build the library by yourself. Waiting for your feedback :)

mo-krautsalat commented 4 years ago

Wow, thank you very much for your effort! I will try it tomorrow and get back to you :)

mo-krautsalat commented 4 years ago

I tried implementing it, by couldn't get the predicate thing for the note selection working. Somehow I can't wrap my head around it. Maybe you could help me (again)? Also I found no way to define the note index directly?

Here is the part of my code with the add/remove note functionality:

        public void AddNote(int message)
        {
            var note = new Melanchall.DryWetMidi.Interaction.Note((SevenBitNumber)message);
            patternBuilder.SetNoteLength(new BarBeatTicksTimeSpan(0, 1, 0));
            patternBuilder.SetVelocity((SevenBitNumber)127);
            patternBuilder.Note(note.GetMusicTheoryNote());

            if (playback != null)
            {
                lastTime = playback.GetCurrentTime(TimeSpanType.BarBeatTicks);
                playback.Dispose();
            }

            Playback();
        }

        public void RemoveNote(int message, long bar, long beat)
        {
            SevenBitNumber nNumber = (SevenBitNumber)message;

            NoteDescriptor noteDesc = new NoteDescriptor(new Melanchall.DryWetMidi.Interaction.Note(nNumber).GetMusicTheoryNote(), (SevenBitNumber)127, new BarBeatTicksTimeSpan(0, 1, 0));

            NoteSelection noteSelection = new NoteSelection(2, noteDesc);

            patternBuilder.Build().SetNotesState(noteSelection, PatternActionState.Disabled);

            if (playback != null)
            {
                playback.Dispose();
            }

            Playback();
        }

Thanks!

melanchall commented 4 years ago

Hi,

This line contains issues:

patternBuilder.Build().SetNotesState(noteSelection, PatternActionState.Disabled);

First of all, you don't need to create NoteDescriptor. NoteSelection is predicate, all values are passed to it by SetNotesState, and you then decide whether note should be processed or not:

// Disable note with index 2
SetNotesState((index, descriptor) => index == 2, PatternActionState.Disabled);

Also there is another big problem. You call Build and then SetNotesState, but you don't save changed pattern! After you called Build, you should save new pattern and use it for playback, and for calling SetNotesState. I suppose Playback method calls Build again on patternBuilder, but Build creates new instance of Pattern every time you call it so changes made with SetNotesState are lost.

melanchall commented 4 years ago

Also this code

var note = new Melanchall.DryWetMidi.Interaction.Note((SevenBitNumber)message);
patternBuilder.SetNoteLength(new BarBeatTicksTimeSpan(0, 1, 0));
patternBuilder.SetVelocity((SevenBitNumber)127);
patternBuilder.Note(note.GetMusicTheoryNote());

can be simplified.

You can get MusicTheory.Note with this call:

Note.Get((SevenBitNumber)message)

without creating Interaction.Note.

SetNoteLength and SetVelocity are global things. There is no need to call them again for every single note. Just call them once where you create PatternBuilder. If you want to override these values for some notes, just use .Note overloads with length and velocity parameters.

melanchall commented 4 years ago

@mo-krautsalat Any news?

mo-krautsalat commented 4 years ago

Ups, sorry. Yes I got it working the way I wanted!

Thank you very much for your help and effort :)