Skip to content

Commit 1ff4ceb

Browse files
committed
Add song transposition feature
1 parent a4802ee commit 1ff4ceb

14 files changed

Lines changed: 195 additions & 4 deletions

File tree

CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Release date:
55

66
New features:
77

8+
* Add song transposition feature
9+
- Fixes also drum track not taken into account properly
10+
811
Bug fixes:
912

1013
Other:

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ set(QML_SOURCE_FILES
264264
${QML_BASE_DIR}/Editor/MainContextMenu_Line.qml
265265
${QML_BASE_DIR}/Editor/MainContextMenu_Pattern.qml
266266
${QML_BASE_DIR}/Editor/MainContextMenu_Selection.qml
267+
${QML_BASE_DIR}/Editor/MainContextMenu_Song.qml
267268
${QML_BASE_DIR}/Editor/MainContextMenu_Track.qml
268269
${QML_BASE_DIR}/Editor/MouseHandler.qml
269270
${QML_BASE_DIR}/Editor/MuteSoloButtons.qml

src/application/service/editor_service.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,16 @@ void EditorService::requestPatternTranspose(int semitones)
16591659
}
16601660
}
16611661

1662+
void EditorService::requestSongTranspose(int semitones)
1663+
{
1664+
if (auto changes = m_song->transposeSong(semitones); !changes.empty()) {
1665+
m_undoStack->push(std::make_shared<NoteEditCommand>(m_song, std::move(changes), m_state.cursorPosition, m_state.cursorPosition, [this](const Position & pos) {
1666+
emit noteDataAtPositionChanged(pos);
1667+
setIsModified(true);
1668+
}, [this](const Position & pos) { requestPosition(pos); }));
1669+
}
1670+
}
1671+
16621672
void EditorService::requestSelectionCut()
16631673
{
16641674
juzzlin::L(TAG).info() << "Requesting selection cut";

src/application/service/editor_service.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ class EditorService : public QObject
207207
virtual Q_INVOKABLE void requestPatternCopy();
208208
virtual Q_INVOKABLE void requestPatternPaste();
209209
virtual Q_INVOKABLE void requestPatternTranspose(int semitones);
210+
virtual Q_INVOKABLE void requestSongTranspose(int semitones);
210211
virtual Q_INVOKABLE bool hasPatternToPaste() const;
211212

212213
virtual Q_INVOKABLE void requestSelectionCut();

src/domain/pattern.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ std::unique_ptr<Pattern> Pattern::copyWithoutData(size_t newPatternIndex) const
5151
return std::make_unique<Pattern>(newPatternIndex, patternConfig());
5252
}
5353

54+
void Pattern::copySettingsFrom(const Pattern & other)
55+
{
56+
for (const auto & track : other.m_trackOrder) {
57+
if (auto myTrack = trackByIndex(track->index()); myTrack) {
58+
myTrack->setName(track->name());
59+
myTrack->setInstrument(track->instrument());
60+
for (size_t column = 0; column < track->columnCount(); ++column) {
61+
if (column < myTrack->columnCount()) {
62+
myTrack->setColumnName(column, track->columnName(column));
63+
myTrack->setColumnSettings(column, track->columnSettings(column));
64+
}
65+
}
66+
}
67+
}
68+
}
69+
5470
Pattern::PatternConfig Pattern::patternConfig() const
5571
{
5672
PatternConfig config;
@@ -372,11 +388,14 @@ Pattern::PositionList Pattern::insertNoteDataAtPosition(const NoteData & noteDat
372388
return trackByIndexThrow(position.track)->insertNoteDataAtPosition(noteData, position);
373389
}
374390

375-
NoteChangeList Pattern::transposePattern(const Position & position, int semitones) const
391+
NoteChangeList Pattern::transposePattern(const Position & position, int semitones, const DrumTracks & drumTracks) const
376392
{
377393
NoteChangeList changes;
378394
for (const auto & track : m_trackOrder) {
379-
if (track->instrument() && track->instrument()->settings().drumTrack) {
395+
if (drumTracks.contains(track->index())) {
396+
continue;
397+
}
398+
if (drumTracks.empty() && track->instrument() && track->instrument()->settings().drumTrack) {
380399
continue;
381400
}
382401
auto trackPosition = position;

src/domain/pattern.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <map>
2020
#include <memory>
2121
#include <optional>
22+
#include <set>
2223
#include <vector>
2324

2425
#include "note_data.hpp"
@@ -110,7 +111,8 @@ class Pattern
110111
PositionList deleteNoteDataAtPosition(const Position & position);
111112
PositionList insertNoteDataAtPosition(const NoteData & noteData, const Position & position);
112113

113-
NoteChangeList transposePattern(const Position & position, int semitones) const;
114+
using DrumTracks = std::set<size_t>;
115+
NoteChangeList transposePattern(const Position & position, int semitones, const DrumTracks & drumTracks = {}) const;
114116
NoteChangeList transposeTrack(const Position & position, int semitones) const;
115117
NoteChangeList transposeColumn(const Position & position, int semitones) const;
116118

@@ -127,6 +129,7 @@ class Pattern
127129
static PatternU deserializeFromXml(QXmlStreamReader & reader);
128130

129131
std::unique_ptr<Pattern> copyWithoutData(size_t newPatternIndex) const;
132+
void copySettingsFrom(const Pattern & other);
130133

131134
PatternConfig patternConfig() const;
132135

src/domain/song.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ Song::ChangedPositions Song::pasteColumn(size_t patternIndex, size_t trackIndex,
7474

7575
NoteChangeList Song::transposeColumn(const Position & position, int semitones) const
7676
{
77+
if (m_patterns.contains(0)) {
78+
if (auto inst = m_patterns.at(0)->instrument(position.track); inst && inst->settings().drumTrack) {
79+
return {};
80+
}
81+
}
7782
return m_patterns.contains(position.pattern) ? m_patterns.at(position.pattern)->transposeColumn(position, semitones) : NoteChangeList {};
7883
}
7984

@@ -103,6 +108,11 @@ Song::ChangedPositions Song::pasteTrack(size_t patternIndex, size_t trackIndex,
103108

104109
NoteChangeList Song::transposeTrack(const Position & position, int semitones) const
105110
{
111+
if (m_patterns.contains(0)) {
112+
if (auto inst = m_patterns.at(0)->instrument(position.track); inst && inst->settings().drumTrack) {
113+
return {};
114+
}
115+
}
106116
return m_patterns.contains(position.pattern) ? m_patterns.at(position.pattern)->transposeTrack(position, semitones) : NoteChangeList {};
107117
}
108118

@@ -132,7 +142,34 @@ Song::ChangedPositions Song::pastePattern(size_t patternIndex, CopyManager & cop
132142

133143
NoteChangeList Song::transposePattern(const Position & position, int semitones) const
134144
{
135-
return m_patterns.contains(position.pattern) ? m_patterns.at(position.pattern)->transposePattern(position, semitones) : NoteChangeList {};
145+
Pattern::DrumTracks drumTracks;
146+
const auto masterPattern = m_patterns.at(0);
147+
for (size_t trackIndex : masterPattern->trackIndices()) {
148+
if (auto inst = masterPattern->instrument(trackIndex); inst && inst->settings().drumTrack) {
149+
drumTracks.insert(trackIndex);
150+
}
151+
}
152+
return m_patterns.contains(position.pattern) ? m_patterns.at(position.pattern)->transposePattern(position, semitones, drumTracks) : NoteChangeList {};
153+
}
154+
155+
NoteChangeList Song::transposeSong(int semitones) const
156+
{
157+
Pattern::DrumTracks drumTracks;
158+
const auto masterPattern = m_patterns.at(0);
159+
for (size_t trackIndex : masterPattern->trackIndices()) {
160+
if (auto inst = masterPattern->instrument(trackIndex); inst && inst->settings().drumTrack) {
161+
drumTracks.insert(trackIndex);
162+
}
163+
}
164+
165+
NoteChangeList changes;
166+
for (auto && [index, pattern] : m_patterns) {
167+
Position position;
168+
position.pattern = index;
169+
auto patternChanges = pattern->transposePattern(position, semitones, drumTracks);
170+
changes.insert(changes.end(), patternChanges.begin(), patternChanges.end());
171+
}
172+
return changes;
136173
}
137174

138175
Song::ChangedPositions Song::cutSelection(PositionListCR positions, CopyManager & copyManager, const AutomationService & automationService) const

src/domain/song.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class Song
8080
ChangedPositions copyPattern(size_t patternIndex, CopyManager & copyManager, const AutomationService & automationService) const;
8181
ChangedPositions pastePattern(size_t patternIndex, CopyManager & copyManager) const;
8282
NoteChangeList transposePattern(const Position & position, int semitones) const;
83+
NoteChangeList transposeSong(int semitones) const;
8384

8485
using PositionList = std::vector<Position>;
8586
using PositionListCR = const PositionList &;

src/unit_tests/editor_service_test/editor_service_test.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,24 @@ void EditorServiceTest::test_requestPatternTranspose_shouldTransposePattern()
10261026
QCOMPARE(editorService.displayVelocityAtPosition(0, 1, 0, 0), "064");
10271027
}
10281028

1029+
void EditorServiceTest::test_requestSongTranspose_shouldTransposeSong()
1030+
{
1031+
EditorService editorService { std::make_shared<SelectionService>(), std::make_shared<SettingsService>(), std::make_shared<AutomationService>(std::make_shared<PropertyService>()) };
1032+
QSignalSpy noteDataChangedSpy { &editorService, &EditorService::noteDataAtPositionChanged };
1033+
1034+
QVERIFY(editorService.requestPosition(0, 0, 0, 0, 0));
1035+
QVERIFY(editorService.requestNoteOnAtCurrentPosition(1, 3, 64)); // C-3
1036+
1037+
editorService.setCurrentPattern(1);
1038+
QVERIFY(editorService.requestPosition(1, 1, 0, 0, 0));
1039+
QVERIFY(editorService.requestNoteOnAtCurrentPosition(3, 3, 64)); // D-3
1040+
1041+
editorService.requestSongTranspose(1);
1042+
1043+
QCOMPARE(editorService.displayNoteAtPosition(0, 0, 0, 0), "C#3");
1044+
QCOMPARE(editorService.displayNoteAtPosition(1, 1, 0, 0), "D#3");
1045+
}
1046+
10291047
void EditorServiceTest::test_requestSelectionTranspose_shouldTransposeSelection()
10301048
{
10311049
const auto selectionService = std::make_shared<SelectionService>();

src/unit_tests/editor_service_test/editor_service_test.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ private slots:
8080
void test_requestColumnTranspose_shouldTransposeColumn();
8181
void test_requestTrackTranspose_shouldTransposeTrack();
8282
void test_requestPatternTranspose_shouldTransposePattern();
83+
void test_requestSongTranspose_shouldTransposeSong();
8384
void test_requestSelectionTranspose_shouldTransposeSelection();
8485

8586
void test_requestLinearVelocityInterpolationOnColumn_shouldInterpolateVelocities();

0 commit comments

Comments
 (0)