Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Note Types #5902

Merged
merged 27 commits into from
Nov 18, 2023
Merged

Implement Note Types #5902

merged 27 commits into from
Nov 18, 2023

Conversation

IanCaio
Copy link
Contributor

@IanCaio IanCaio commented Feb 3, 2021

This PR moves away from using negative length for notes that are supposed to be played for the length of the sample. Instead, we now use Note Types, which describe the type of note that is being played (for now, either RegularNote or StepNote). The length of a step note is now 1/16th by default, but it doesn't matter if you resize it because InstrumentTrack::play will create a NotePlayHandle with 0 frames if the type of the note is StepNote.

The StepNotes are drawn now as red notes on the PianoRoll. BeatPattern TCOs are drawn in the Song Editor with the assigned BBPatternBackground color (which is grey on default theme).

On the image, TripleOsc with a MelodyPattern and AFP with a BeatPattern and a MelodyPattern that has both step notes and regular notes (opened on the PianoRoll):
NoteType

There's a qWarning left on TimePos::frames that is supposed to be removed. Keeping it just to check if there's any code left creating negative length notes. Didn't rush to remove it since I'm not even sure this will be reviewed before the refactor.

Fixes #5339
Fixes #5340

	Starts implementing Note Types. The two available types are
RegularNote and StepNote. PianoRoll now paints the color with a
different color for StepNotes. Pattern::addStep now sets the type of the
note to StepNote.
	Negative size is still used to signal a step note.
	Updates the methods noteAtStep(), addStepNote() and checkType()
from Pattern.cpp to account for the note type and not the note length.
	PatternView::paintEvent now draws the pattern if the pattern
type is BeatPattern and TCOs aren't fixed (Song Editor). Color used is
still the BeatPattern color (grey) and the conditional doesn't look very
nice and can be improved.
	Pattern::beatPatternLength was also updated so it accounts for
the note type not note length. Review this method, as it looks a bit
weird (particularly the second conditional).
	Now, instead of TimePos returning 0 for negative lengths, we
create a NotePlayHandle with 0 frames when the note type is StepNote on
InstrumentTrack::play.
	Improves a conditional inside PatternView::paintEvent by
reversing the order in which they are executed.
@IanCaio IanCaio added the after-refactor PRs that will need to be rebased after the planned code refactor (#5592) label Feb 3, 2021
@LmmsBot
Copy link

LmmsBot commented Feb 3, 2021

🤖 Hey, I'm @LmmsBot from github.com/lmms/bot and I made downloads for this pull request, click me to make them magically appear! 🎩

Windows

Linux

macOS

🤖
{"platform_name_to_artifacts": {"Windows": [{"artifact": {"title": {"title": "32-bit", "platform_name": "Windows"}, "link": {"link": "https://13552-15778896-gh.circle-artifacts.com/0/lmms-1.3.0-alpha.1.109%2Bgfcd5797af-mingw-win32.exe"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/13552?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}, {"artifact": {"title": {"title": "64-bit", "platform_name": "Windows"}, "link": {"link": "https://13553-15778896-gh.circle-artifacts.com/0/lmms-1.3.0-alpha.1.109%2Bgfcd5797af-mingw-win64.exe"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/13553?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}, {"artifact": {"title": {"title": "32-bit", "platform_name": "Windows"}, "link": {"link": "https://ci.appveyor.com/api/buildjobs/pjgko0npbh7265ay/artifacts/build/lmms-1.3.0-alpha-msvc2017-win32.exe"}}, "build_link": "https://ci.appveyor.com/project/Lukas-W/lmms/builds/38753969"}, {"artifact": {"title": {"title": "64-bit", "platform_name": "Windows"}, "link": {"link": "https://ci.appveyor.com/api/buildjobs/xlydwesm96ll49ho/artifacts/build/lmms-1.3.0-alpha-msvc2017-win64.exe"}}, "build_link": "https://ci.appveyor.com/project/Lukas-W/lmms/builds/38753969"}], "Linux": [{"artifact": {"title": {"title": "(AppImage)", "platform_name": "Linux"}, "link": {"link": "https://13555-15778896-gh.circle-artifacts.com/0/lmms-1.3.0-alpha.1.109%2Bgfcd5797-linux-x86_64.AppImage"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/13555?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}], "macOS": [{"artifact": {"title": {"title": "", "platform_name": "macOS"}, "link": {"link": "https://13551-15778896-gh.circle-artifacts.com/0/lmms-1.3.0-alpha.1.109%2Bgfcd5797af-mac10.14.dmg"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/13551?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}]}, "commit_sha": "f5cc8893f65357a510dd8c6b6a018daa854f4588"}

@zonkmachine
Copy link
Member

This also closes #1681. There is an issue brought up somewhere around #1681 (comment) concerning representation of beat notes in tco's that perhaps should have it's own ticket. It's fixed in this PR.

@IanCaio
Copy link
Contributor Author

IanCaio commented Feb 3, 2021

This also closes #1681. There is an issue brought up somewhere around #1681 (comment) concerning representation of beat notes in tco's that perhaps should have it's own ticket. It's fixed in this PR.

True, it partially solves it. The notes that are off-beat will still not show up on the BB Step Note editor and neither will it be displayed as a TCO on that case. I think it's better to leave it that way until we can implement allowing step notes to be added from the Piano Roll.

I'm about to push the upgrade routine which I totally forgot. Can't load older projects properly without it.

	Adds an upgrade method that converts notes with negative length
to StepNotes, so old projects can be loaded properly.
	Explicitly set the Note::RegularNote value as 0.
	Make the default "type" value "0", so notes without a type are
loaded as RegularNotes.
@IanCaio
Copy link
Contributor Author

IanCaio commented Feb 3, 2021

Done. Upgrade method was added, older projects should be loaded properly now.

@PhysSong
Copy link
Member

PhysSong commented Feb 7, 2021

Note: MIDI export may require some additional changes.

@thmueller64
Copy link
Contributor

I tested this PR and it works as intended. The PR #5502 should be closed in favor of this one.
There is one thing that #5502 handles, which should also be taken care of here:

  1. Draw pattern in BBEditor
  2. Right click on the pattern --> Open in PianoRoll
  3. Copy the notes and paste them at the end

Expected Result:

  1. Length of the pattern shown in the BBEditor changes accordingly

Actual Result:

  1. Length of the BB pattern stays the same (the pasted notes are hidden essentially)

In the linked PR, this is handled with the adjustSteps function.

@IanCaio
Copy link
Contributor Author

IanCaio commented Feb 7, 2021

Note: MIDI export may require some additional changes.

True, it might not though because it uses saveSettings I guess. So if I update it on the note class I guess it should save and load properly on the XML pattern file.

I tested this PR and it works as intended. The PR #5502 should be closed in favor of this one.
There is one thing that #5502 handles, which should also be taken care of here:

You're right. I'm not sure there's an open issue for that (it seems to be the behavior on master) but we can fix it too. I was looking how you did it, you added an adjustStep call on addNote, there should be one on removeNote too right? I thought about connecting it to dataChanged instead but quickly looking I see there might be issues, as other methods like addStep also emit it and it might recalculate the steps unnecessarily more than once. I just worry that addNote and removeNote doesn't cover every situation where the pattern changes (was thinking undo/redo for example, or copy/paste, even import pattern in the future).

@PhysSong
Copy link
Member

PhysSong commented Feb 8, 2021

Note: MIDI export may require some additional changes.

I wrote this warning because it depends on the negative length of BB notes.

@thmueller64
Copy link
Contributor

there should be one on removeNote too right?

I deliberately didn't add it there:
Say the user clicks on "Add steps" and creates a pattern on the added steps. If the user deselects all of the steps in the extended range for a moment, the range is shrunk immediately. I thought this would be annoying.

I just worry that addNote and removeNote doesn't cover every situation where the pattern changes (was thinking undo/redo for example, or copy/paste, even import pattern in the future).

undo/redo, copy/paste works fine, I testet it with #5502. We can open a separate issue for this one.

@IanCaio
Copy link
Contributor Author

IanCaio commented Feb 8, 2021

I deliberately didn't add it there:
Say the user clicks on "Add steps" and creates a pattern on the added steps. If the user deselects all of the steps in the extended range for a moment, the range is shrunk immediately. I thought this would be annoying.

Oh, that makes sense, so it would only grow automatically but you'd need to remove the steps manually to shrink it again.

undo/redo, copy/paste works fine, I testet it with #5502. We can open a separate issue for this one.

Awesome, I thought there might be some issues because some of them use the XML state and doesn't make calls to addNote (loadSettings), but then, they also copy the steps value, so it should be fine. PianoRoll::pasteNotes seems to use addNote to paste the notes so it seems to behave well too.

I think a separate PR would be better in the testing/bug-hunting aspect, but that's definitely something worth adding. Or I could just add it to this one, I don't mind it neither 😁

NoteVector const & noteCollection = m_pat->m_notes;
if( m_pat->m_patternType == Pattern::MelodyPattern && !noteCollection.empty() )

// Beat pattern paint event
Copy link
Contributor

@Veratil Veratil Feb 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're moving it, let's fix the style before merge? Or just let the eventual clang-format/tidy do it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I ended up not doing it because I moved a big block but didn't change it and wasn't sure how it would show in the diff. Can I leave this style fix for after it's decided whether this will be merged before or after refactor? Because if it's merged after I'm probably going to have to rebase against the fixed-style master anyways

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with leaving for clang PR.

src/tracks/Pattern.cpp Outdated Show resolved Hide resolved
src/tracks/Pattern.cpp Outdated Show resolved Hide resolved
	- Changes "addStepNote" so "checkType" isn't called twice in a
row.
	- Changes style on a one line conditional.
@IanCaio
Copy link
Contributor Author

IanCaio commented Feb 11, 2021

Done! Except for the code style formatting of the big block of code that was moved.

I was thinking, another alternative was to change addNote so it had a type as a parameter and having RegularNote as default. But the current solution is good and I'm keeping it for now.

	Reduces number of lines by using ternary expression.
@superpaik
Copy link
Contributor

It works fine to me, except in one case.
If the sample duration is longer than the beat, it seems that ends before the beat ends. Check with house_loop01.ogg at 140bpm, it sounds for like 3 and 3/4 beats instead of 4 beats. There is a silence between beats. It doesn't happen if you play the sample as an audiofile directly for 4 beats.

Not strictly related to the functionality but more of UX. I think it would be great to have the possibility to add new Step Notes directly from the pianoroll (now it's only possible through copy-and-paste). Maybe a tool/option to change between type notes would be great.

@IanCaio
Copy link
Contributor Author

IanCaio commented Feb 15, 2021

It works fine to me, except in one case.
If the sample duration is longer than the beat, it seems that ends before the beat ends. Check with house_loop01.ogg at 140bpm, it sounds for like 3 and 3/4 beats instead of 4 beats. There is a silence between beats. It doesn't happen if you play the sample as an audiofile directly for 4 beats.

I think the issue you experienced is likely this one #2074 . It's probably more noticeable in the piano roll than it is in the BB editor though.

Not strictly related to the functionality but more of UX. I think it would be great to have the possibility to add new Step Notes directly from the pianoroll (now it's only possible through copy-and-paste). Maybe a tool/option to change between type notes would be great.

That's the idea! The PR currently only implements the base for allowing that, but following enhancements should allow you to add this type of note directly from the PianoRoll. I even envision other types of notes in the future, like "Arpeggio" notes, so we can move away from the currently buggy Arpeggiator and have a more flexible and visual one.

For now, since this will possibly only be merged after the refactor I'd rather just add the base functionality for now and leave those enhancements for later. We'd have to decide how it would work though: Would the Draw mode icon have a dropdown menu where you chose the type of note? Or a context menu for notes on the piano roll? Maybe even both, thought it would require UX changes since the right click is currently used for deleting notes.

@superpaik
Copy link
Contributor

I think the issue you experienced is likely this one #2074 . It's probably more noticeable in the piano roll than it is in the BB editor though.

Yes, it seems the same bug.

As for your other comment, I agree with you. Let's wait for refactoring.

Some ideas as how I see they could work (for the future): I think that, in general, you'd use Regular or Step Notes, but I think it's kind of weird to mix them up (I'm not saying that it shouldn't be addressed, but I see it as an exception). So, if the piano roll is open form inside the BBEditor, I'd set the draw to Step notes by default, and if it's open from the song editor, the Regular Notes should by the default. Maybe, to change the draw mode, just by clicking on the draw icon it could go from one type to the other. Also, the "last note" draw option should have into account the different type notes, so it's easier for the user to change note type.

@IanCaio
Copy link
Contributor Author

IanCaio commented Mar 4, 2021

Reminder to self:

Note *note = *it;
TimePos len = note->length();
if( len < 0 )
{
len = 4;
}

This piece of code on the PianoRoll::mousePressEvent is there because of negative length notes and with this PR will not be necessary anymore.

Out of the scope of this PR but mousePressEvent needs some serious refactoring. Right after that code block there's this conditional:

if( pos_ticks >= note->pos() &&
len > 0 &&

len > 0 will never be false since it's set to 4 when it's negative and as far as I know there aren't notes with length equal to 0. I'll hold any attempt on refactoring this method for after the PRs that affect it are merged though (IIRR only #5848 and #5857)

@Veratil
Copy link
Contributor

Veratil commented Mar 4, 2021

Out of the scope of this PR but mousePressEvent needs some serious refactoring.

I've been slowly hacking at all mouse events in PianoRoll. I have a branch locally which has rewritten a lot to use switch's and organizing the code to be a lot more readable. It's still a big WIP though.

@IanCaio
Copy link
Contributor Author

IanCaio commented Mar 5, 2021

I've been slowly hacking at all mouse events in PianoRoll. I have a branch locally which has rewritten a lot to use switch's and organizing the code to be a lot more readable. It's still a big WIP though.

I was actually thinking of asking if you wanted to team up fixing those mouse event methods, maybe even coming up with controls that were more up-to-date to the current new features: One thing I was thinking of was implementing a context-menu, we could move the 4 note tools and the glue tool to that context menu, opening space for many others without filling the window toolbar.

It's awesome that you have this WIP going already, just moving to a more organized switch will improve it greatly 😄

@IanCaio
Copy link
Contributor Author

IanCaio commented Mar 5, 2021

Conflicts with master fixed

@IanCaio
Copy link
Contributor Author

IanCaio commented Oct 17, 2023

Fixed conflict. @DomClark @PhysSong @Veratil You have started reviewing this one in the past, does it look good now? Sakertooth approved so there's just one approval pending.

There's one fail on the linux build, but apparently it's related to setting up git, not sure why.

include/DataFile.h Outdated Show resolved Hide resolved
include/Note.h Outdated Show resolved Hide resolved
src/core/TimePos.cpp Show resolved Hide resolved
Copy link
Member

@messmerd messmerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple minor things

src/core/Note.cpp Outdated Show resolved Hide resolved
src/gui/clips/MidiClipView.cpp Outdated Show resolved Hide resolved
src/gui/clips/MidiClipView.cpp Outdated Show resolved Hide resolved
src/gui/clips/MidiClipView.cpp Outdated Show resolved Hide resolved
	- Addresses code review from PhysSong and Messmerd
	- Notes were not being draw on the song editor for BeatClips.
This commit fixes this.
@IanCaio
Copy link
Contributor Author

IanCaio commented Oct 17, 2023

Thanks guys, code reviews were addressed! 😄

Last commit fixes a small conditional issue on the drawing of MidiClips on the song editor (Beat clips weren't having their notes drawn). It's a small fix, if you guys can check if it's good then it's all done. I could have left the conditional as only else if(!noteCollection.empty()), cause right now the clip types will be either MelodyClip or BeatClip, so the second conditional (m_clip->m_clipType == MidiClip::Type::MelodyClip || m_clip->m_clipType == MidiClip::Type::BeatClip) is redundant, but it anticipates new clip types being added, so I thought better making it explicit.

@DomClark DomClark self-requested a review October 17, 2023 21:34
@messmerd
Copy link
Member

@IanCaio It looks like <cassert> needs to be included in TimePos.cpp to fix the build.

	- Adds header to use assert() on TimePos.cpp
@IanCaio
Copy link
Contributor Author

IanCaio commented Oct 18, 2023

@IanCaio It looks like <cassert> needs to be included in TimePos.cpp to fix the build.

Weird, for some reason it compiled fine here, but I just pushed a new commit with the header. Let's see if the builds work now!

@IanCaio
Copy link
Contributor Author

IanCaio commented Oct 24, 2023

@PhysSong @messmerd Does the PR looks good to you after the requested changes?

@DomClark I noticed you self-requested a review, in case there's a second approval would you like for us to wait some time so you can do it?

Copy link
Member

@messmerd messmerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a few things I must have missed last time.

I'll test this PR soon and if all goes well, I think it should be about ready to merge

plugins/MidiExport/MidiExport.cpp Outdated Show resolved Hide resolved
plugins/MidiExport/MidiExport.cpp Outdated Show resolved Hide resolved
@@ -113,6 +114,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks,
int base_pitch = 0;
double base_volume = 1.0;
int base_time = 0;
int base_beatLen = 16;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there are a few places where the default step note length of 16 is hard-coded in. You should probably create a constexpr constant for it and use that instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just followed the example of the other variables, that value is more like a default in case there's no stored value on the XML. Should I change it to be constexpr int base_beatLen = 16; here and in the other method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I meant if you want the default step note length to be a 1/16th note, you shouldn't have "16" hard-coded in 3 or 4 different places. You should probably use a static constexpr member in the Note class called DefaultStepNoteLength or something like that:

// Inside Note class in Note.h
static constexpr int DefaultStepNoteLength = 16; // 16th note

Then replace every "16" which you're using to represent the default length of step notes with Note::DefaultStepNoteLength.

The point of this is to avoid magic numbers and have the default step note length defined in a single place so it's easy to change later if we ever decide to do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, got it! I wrote the changes here, I'm just double checking because I think base_beatLen is actually not related to the default step note length of 1/16th (the one that shows on the piano roll):

Updates MidiExport to use Note Types

  • Now MidiExport is updated to use note types instead of relying
    on negative length notes.
  • For that change it was necessary to find a way of letting
    MidiExport know how long step notes should be. The solution found was to
    add an attribute to the Instrument XML called "beatlen", which would
    hold the number of frames of the instrument's beat. That would be
    converted to ticks, so we could calculate how long the MIDI notes would
    have to be to play the whole step note. If the attribute was not found,
    the default value of 16 ticks would be used as a length of step notes,
    as a fallback.

I think I'd need another constexpr for the beatLen fallback value. Should I have it on "InstrumentTrack.h"?

IanCaio and others added 2 commits October 25, 2023 23:15
Fixes style on some lines

Co-authored-by: Dalton Messmer <[email protected]>
	- Some changes were reverted on MidiExport and InstrumentTrack.
We were storing the beat length on the XML of Instrument Tracks, but in
reality the beat length is a per note attribute, and some instruments
could run into a segmentation fault when calling beat length without a
NotePlayHandle (i.e.: AFP). Because of that I reverted this change, so
the beat length is not stored on the XML anymore, and instead we have a
magic number on the MidiExport class that holds a default beat length
which is actually an upper limit for the MIDI notes of step notes. In
the future we can improve this by finding a way to store the beat length
on the note class to use it instead. The MidiExport logic is not
worsened at all because previously the beat length wasn't even
considered during export (it was actually improved making the exported
notes extend until the next one instead of cutting shorter).
@IanCaio
Copy link
Contributor Author

IanCaio commented Nov 3, 2023

I reverted one of the changes in MidiExport. Turns out beatLen is a per note attribute, not per instrument (it's just that some instruments will use the same beatLen for every note, but for AFP for example calling ::beatLen() with a nullptr will result in a SegFault). So instead we would need to store the attribute on every note on the XML, but it also requires a NotePlayHandle to be calculated. Since I didn't figure out a practical and not very complex way of storing the beat length on a member variable of the Note class, it wouldn't be easily accessible on the MidiExport processing.

So now a magic number is being used as a default beat length, that in practice just represents the upper limit of the length of a step note on the MIDI in ticks. I left a TODO comment so this is clear, and in no way this worsens the MidiExport logic, since the previous code didn't care about the beat length at all. It's probably even improved because before the step notes were cut short since the negative length of step notes was used as a limit instead.

That should be the last change hopefully. @DomClark do you still wish to review this? And the other reviewers can check whether everything they suggested was covered?

@IanCaio
Copy link
Contributor Author

IanCaio commented Nov 15, 2023

Does anyone still wishes to review this PR?

@zonkmachine zonkmachine self-requested a review November 15, 2023 22:31
Copy link
Member

@zonkmachine zonkmachine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and my projects work as they should. Two issues are solved by this and I can confirm the issues and the fixes.

There's a qWarning left on TimePos::frames that is supposed to be removed. Keeping it just to check if there's any code left creating negative length notes. Didn't rush to remove it since I'm not even sure this will be reviewed before the refactor.

This is in the top post. It looks like this has been fixed, right?

@zonkmachine
Copy link
Member

@IanCaio I introduced a new merge conflict in the latest merge. Just a final 'touch up' and then merge, I say.

src/core/TimePos.cpp Outdated Show resolved Hide resolved
@IanCaio
Copy link
Contributor Author

IanCaio commented Nov 18, 2023

Just fixed the conflicts and the order of included files

@messmerd messmerd changed the title Implements Note Types (Fixes #5339 and #5340) Implement Note Types Nov 18, 2023
@messmerd messmerd merged commit 17c9198 into LMMS:master Nov 18, 2023
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
10 participants