Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/AutomatableModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject
return (std::round(v) != 0);
}

float oldValue() const { return m_oldValue; }

template<class T>
inline T value( int frameOffset = 0 ) const
Expand Down
6 changes: 2 additions & 4 deletions include/InstrumentTrack.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,19 @@
#ifndef LMMS_INSTRUMENT_TRACK_H
#define LMMS_INSTRUMENT_TRACK_H


#include "AudioBusHandle.h"
#include "InstrumentFunctions.h"
#include "InstrumentSoundShaping.h"
#include "Microtuner.h"
#include "Midi.h"
#include "MidiEventProcessor.h"
#include "MidiPort.h"
#include "MixerChannelLcdModel.h"
#include "NotePlayHandle.h"
#include "Piano.h"
#include "Plugin.h"
#include "Track.h"


namespace lmms
{

Expand Down Expand Up @@ -262,7 +261,6 @@ protected slots:
void updatePitchRange();
void updateMixerChannel();


private:
void processCCEvent(int controller);

Expand Down Expand Up @@ -296,7 +294,7 @@ protected slots:

FloatModel m_pitchModel;
IntModel m_pitchRangeModel;
IntModel m_mixerChannelModel;
MixerChannelLcdModel m_mixerChannelModel;
BoolModel m_useMasterPitchModel;

Instrument * m_instrument;
Expand Down
11 changes: 11 additions & 0 deletions include/Mixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,15 @@ class MixerChannel : public ThreadableJob
void incrementDeps();
void processed();

int useCount() const { return m_useCount; }

void incrementUseCount() { ++m_useCount; }
void decrementUseCount() { --m_useCount; }

private:
void doProcessing() override;
int m_channelIndex;
int m_useCount;
std::optional<QColor> m_color;
};

Expand Down Expand Up @@ -214,6 +220,11 @@ class LMMS_EXPORT Mixer : public Model, public JournallingObject

MixerRouteVector m_mixerRoutes;

signals:
void channelCreated(int index);
void channelDeleted(int index);
void channelsSwapped(int fromIndex, int toIndex);

private:
// the mixer channels in the mixer. index 0 is always master.
std::vector<MixerChannel*> m_mixerChannels;
Expand Down
56 changes: 56 additions & 0 deletions include/MixerChannelLcdModel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* MixerChannelLcdModel.h
*
* Copyright (c) 2026 saker <sakertooth@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/

#ifndef LMMS_MIXER_CHANNEL_LCD_MODEL_H
#define LMMS_MIXER_CHANNEL_LCD_MODEL_H

#include "AutomatableModel.h"

namespace lmms {
/**
* @brief An @ref IntModel that keeps track of its assigned mixer channel.
*
* This model tracks its assigned mixer channel. It is a subclass of IntModel that adds functionality to handle channel
* creation, deletion, and swapping. Both the value and valid range are automatically updated to reflect these changes.
*/
class MixerChannelLcdModel : public IntModel
{
public:
MixerChannelLcdModel(Model* parent = nullptr);
~MixerChannelLcdModel();

MixerChannelLcdModel(const MixerChannelLcdModel&) = delete;
MixerChannelLcdModel(MixerChannelLcdModel&&) = delete;
MixerChannelLcdModel& operator=(const MixerChannelLcdModel&) = delete;
MixerChannelLcdModel& operator=(MixerChannelLcdModel&&) = delete;

private:
void channelsSwapped(int fromIndex, int toIndex);
void channelDeleted(int index);
void channelCreated(int index);
};

} // namespace lmms

#endif // LMMS_MIXER_CHANNEL_LCD_MODEL_H
2 changes: 0 additions & 2 deletions include/MixerView.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ private slots:
QWidget* m_racksWidget;
Mixer* m_mixer;

void updateMaxChannelSelector();

friend class MixerChannelView;
} ;

Expand Down
3 changes: 2 additions & 1 deletion include/SampleTrack.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#define LMMS_SAMPLE_TRACK_H

#include "AudioBusHandle.h"
#include "MixerChannelLcdModel.h"
#include "Track.h"


Expand Down Expand Up @@ -94,7 +95,7 @@ public slots:
private:
FloatModel m_volumeModel;
FloatModel m_panningModel;
IntModel m_mixerChannelModel;
MixerChannelLcdModel m_mixerChannelModel;
AudioBusHandle m_audioBusHandle;
bool m_isPlaying;

Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ set(LMMS_SRCS
core/MicroTimer.cpp
core/Microtuner.cpp
core/MixHelpers.cpp
core/MixerChannelLcdModel.cpp
core/Model.cpp
core/ModelVisitor.cpp
core/Note.cpp
Expand Down
134 changes: 14 additions & 120 deletions src/core/Mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ MixerChannel::MixerChannel( int idx, Model * _parent ) :
m_lock(),
m_queued( false ),
m_dependenciesMet(0),
m_channelIndex(idx)
m_channelIndex(idx),
m_useCount(0)
{
m_buffer.allocateInterleavedBuffer();
}
Expand Down Expand Up @@ -271,6 +272,7 @@ int Mixer::createChannel()
m_mixerChannels[index]->m_muteModel.setValue(true);
}

emit channelCreated(index);
return index;
}

Expand Down Expand Up @@ -331,50 +333,6 @@ void Mixer::deleteChannel( int index )
// channel deletion is performed between mixer rounds
Engine::audioEngine()->requestChangeInModel();

// go through every instrument and adjust for the channel index change
TrackContainer::TrackList tracks;

auto& songTracks = Engine::getSong()->tracks();
auto& patternStoreTracks = Engine::patternStore()->tracks();
tracks.insert(tracks.end(), songTracks.begin(), songTracks.end());
tracks.insert(tracks.end(), patternStoreTracks.begin(), patternStoreTracks.end());

for( Track* t : tracks )
{
if( t->type() == Track::Type::Instrument )
{
auto inst = dynamic_cast<InstrumentTrack*>(t);
int val = inst->mixerChannelModel()->value(0);
if( val == index )
{
// we are deleting this track's channel send
// send to master
inst->mixerChannelModel()->setValue(0);
}
else if( val > index )
{
// subtract 1 to make up for the missing channel
inst->mixerChannelModel()->setValue(val-1);
}
}
else if( t->type() == Track::Type::Sample )
{
auto strk = dynamic_cast<SampleTrack*>(t);
int val = strk->mixerChannelModel()->value(0);
if( val == index )
{
// we are deleting this track's channel send
// send to master
strk->mixerChannelModel()->setValue(0);
}
else if( val > index )
{
// subtract 1 to make up for the missing channel
strk->mixerChannelModel()->setValue(val-1);
}
}
}

MixerChannel * ch = m_mixerChannels[index];

// delete all of this channel's sends and receives
Expand Down Expand Up @@ -414,6 +372,8 @@ void Mixer::deleteChannel( int index )
}
}

emit channelDeleted(index);

Engine::audioEngine()->doneChangeInModel();
}

Expand All @@ -422,60 +382,23 @@ void Mixer::deleteChannel( int index )
void Mixer::moveChannelLeft( int index )
{
// can't move master or first channel
if (index <= 1 || static_cast<std::size_t>(index) >= m_mixerChannels.size())
{
return;
}
if (index <= 1 || static_cast<std::size_t>(index) >= m_mixerChannels.size()) { return; }

// channels to swap
int a = index - 1, b = index;

// check if m_lastSoloed is one of our swaps
if (m_lastSoloed == a) { m_lastSoloed = b; }
else if (m_lastSoloed == b) { m_lastSoloed = a; }

// go through every instrument and adjust for the channel index change
const TrackContainer::TrackList& songTrackList = Engine::getSong()->tracks();
const TrackContainer::TrackList& patternTrackList = Engine::patternStore()->tracks();

for (const auto& trackList : {songTrackList, patternTrackList})
{
for (const auto& track : trackList)
{
if (track->type() == Track::Type::Instrument)
{
auto inst = (InstrumentTrack*)track;
int val = inst->mixerChannelModel()->value(0);
if( val == a )
{
inst->mixerChannelModel()->setValue(b);
}
else if( val == b )
{
inst->mixerChannelModel()->setValue(a);
}
}
else if (track->type() == Track::Type::Sample)
{
auto strk = (SampleTrack*)track;
int val = strk->mixerChannelModel()->value(0);
if( val == a )
{
strk->mixerChannelModel()->setValue(b);
}
else if( val == b )
{
strk->mixerChannelModel()->setValue(a);
}
}
}
}

// Swap positions in array
qSwap(m_mixerChannels[index], m_mixerChannels[index - 1]);
std::swap(m_mixerChannels[index], m_mixerChannels[index - 1]);

// Update m_channelIndex of both channels
m_mixerChannels[index]->setIndex(index);
m_mixerChannels[index - 1]->setIndex(index - 1);

emit channelsSwapped(index, index - 1);
}


Expand Down Expand Up @@ -891,41 +814,12 @@ void Mixer::validateChannelName( int index, int oldIndex )

bool Mixer::isChannelInUse(int index)
{
// check if the index mixer channel receives audio from any other channel
if (!m_mixerChannels[index]->m_receives.empty())
{
return true;
}
assert(index >= 0 && index < m_mixerChannels.size());

// check if the destination mixer channel on any instrument or sample track is the index mixer channel
TrackContainer::TrackList tracks;

auto& songTracks = Engine::getSong()->tracks();
auto& patternStoreTracks = Engine::patternStore()->tracks();
tracks.insert(tracks.end(), songTracks.begin(), songTracks.end());
tracks.insert(tracks.end(), patternStoreTracks.begin(), patternStoreTracks.end());

for (const auto t : tracks)
{
if (t->type() == Track::Type::Instrument)
{
auto inst = dynamic_cast<InstrumentTrack*>(t);
if (inst->mixerChannelModel()->value() == index)
{
return true;
}
}
else if (t->type() == Track::Type::Sample)
{
auto strack = dynamic_cast<SampleTrack*>(t);
if (strack->mixerChannelModel()->value() == index)
{
return true;
}
}
}
// check if the index mixer channel receives audio from any other channel
if (!m_mixerChannels[index]->m_receives.empty()) { return true; }

return false;
return m_mixerChannels[index]->useCount() > 0;
}


Expand Down
Loading
Loading