-
-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Sample Track Recording with Jack backend #7567
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -45,7 +45,7 @@ class Track; | |||||
class SampleRecordHandle : public PlayHandle | ||||||
{ | ||||||
public: | ||||||
SampleRecordHandle( SampleClip* clip ); | ||||||
SampleRecordHandle(SampleClip* clip, TimePos startRecordTimeOffset); | ||||||
~SampleRecordHandle() override; | ||||||
|
||||||
void play( SampleFrame* _working_buffer ) override; | ||||||
|
@@ -70,6 +70,9 @@ class SampleRecordHandle : public PlayHandle | |||||
PatternTrack* m_patternTrack; | ||||||
SampleClip * m_clip; | ||||||
|
||||||
// The offset from the start of m_track that the record has | ||||||
// started from. | ||||||
TimePos m_startRecordTimeOffset; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} ; | ||||||
|
||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -467,7 +467,17 @@ bool DataFile::copyResources(const QString& resourcesDir) | |
{ | ||
// Get absolute path to resource | ||
bool error; | ||
QString resPath = PathUtil::toAbsolute(el.attribute(*res), &error); | ||
auto attribute = el.attribute(*res); | ||
|
||
// Skip empty resources. In some cases, there might be an | ||
// alternative way of actually representing the data (e.g. data element for SampleTCO) | ||
if (attribute.isEmpty()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please explain exactly why this was necessary to be in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, this is mentioned in #5990 : #5990 (comment) . I still wonder if it could break anything else? |
||
{ | ||
++res; | ||
continue; | ||
} | ||
|
||
QString resPath = PathUtil::toAbsolute(attribute, &error); | ||
// If we are running without the project loaded (from CLI), "local:" base | ||
// prefixes aren't converted, so we need to convert it ourselves | ||
if (error) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,7 @@ AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam) | |
, m_midiClient(nullptr) | ||
, m_tempOutBufs(new jack_default_audio_sample_t*[channels()]) | ||
, m_outBuf(new SampleFrame[audioEngine()->framesPerPeriod()]) | ||
, m_inBuf(new SampleFrame[audioEngine()->framesPerPeriod()]) | ||
, m_framesDoneInCurBuf(0) | ||
, m_framesToDoInCurBuf(0) | ||
{ | ||
|
@@ -66,6 +67,8 @@ AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam) | |
if (successful) { | ||
connect(this, SIGNAL(zombified()), this, SLOT(restartAfterZombified()), Qt::QueuedConnection); | ||
} | ||
|
||
m_supportsCapture = true; | ||
} | ||
|
||
|
||
|
@@ -90,6 +93,7 @@ AudioJack::~AudioJack() | |
delete[] m_tempOutBufs; | ||
|
||
delete[] m_outBuf; | ||
delete[] m_inBuf; | ||
} | ||
|
||
|
||
|
@@ -154,6 +158,10 @@ bool AudioJack::initJackClient() | |
clientName.toLatin1().constData(), jack_get_client_name(m_client)); | ||
} | ||
|
||
m_inputFrameBuffer = new jack_default_audio_sample_t[channels() * jack_get_buffer_size(m_client)]; | ||
|
||
jack_set_buffer_size_callback(m_client, setBufferSizeCallback, this); | ||
|
||
// set process-callback | ||
jack_set_process_callback(m_client, staticProcessCallback, this); | ||
|
||
|
@@ -167,6 +175,10 @@ bool AudioJack::initJackClient() | |
QString name = QString("master out ") + ((ch % 2) ? "R" : "L") + QString::number(ch / 2 + 1); | ||
m_outputPorts.push_back( | ||
jack_port_register(m_client, name.toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); | ||
|
||
QString input_name = QString("master in ") + ((ch % 2) ? "R" : "L") + QString::number(ch / 2 + 1); | ||
m_inputPorts.push_back(jack_port_register(m_client, input_name.toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); | ||
|
||
if (m_outputPorts.back() == nullptr) | ||
{ | ||
printf("no more JACK-ports available!\n"); | ||
|
@@ -286,11 +298,17 @@ void AudioJack::renamePort(AudioPort* port) | |
} | ||
|
||
|
||
int AudioJack::setBufferSizeCallback(jack_nframes_t nframes, void* udata) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you know if this is allowed to do allocations? I just ask because some callbacks must happen in realtime-context and thus are not allowed to do slow things like My guess is that realtime-safety does not matter when a buffer size changes, but I could not tell from the docs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to Perplexity AI it is called in a non-realtime thread. However, I wasn't able to find that information in the pointers it gave either. 🤔 So it should be taken with a big grain of salt. On the other hand, it would be strange if that information was communicated on the realtime audio thread because it is very likely that clients need to allocate memory as well if that event happens. Perplexitiy blurbYes, the callback that informs about changed buffer sizes in the JACK audio API is called outside of the audio thread. This callback, known as the buffer size callback, is executed in a separate non-real-time thread[1][2].The buffer size callback is registered using the It's important to note that:
This design allows for more flexibility in handling buffer size changes without interfering with the real-time audio processing thread, which has strict timing requirements. Citations: |
||
{ | ||
auto thisClass = static_cast<AudioJack*>(udata); | ||
delete[] thisClass->m_inputFrameBuffer; | ||
thisClass->m_inputFrameBuffer = new jack_default_audio_sample_t[thisClass->channels() * nframes]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider to use an |
||
return 0; | ||
} | ||
|
||
|
||
int AudioJack::processCallback(jack_nframes_t nframes) | ||
{ | ||
|
||
// do midi processing first so that midi input can | ||
// add to the following sound processing | ||
if (m_midiClient && nframes > 0) | ||
|
@@ -356,6 +374,16 @@ int AudioJack::processCallback(jack_nframes_t nframes) | |
} | ||
} | ||
|
||
for (int c = 0; c < channels(); ++c) | ||
{ | ||
jack_default_audio_sample_t* jack_input_buffer = (jack_default_audio_sample_t*) jack_port_get_buffer(m_inputPorts[c], nframes); | ||
|
||
for (jack_nframes_t frame = 0; frame < nframes; frame++) | ||
{ | ||
m_inputFrameBuffer[frame * channels() + c] = jack_input_buffer[frame]; | ||
} | ||
} | ||
audioEngine()->pushInputFrames ((SampleFrame*) m_inputFrameBuffer, nframes); | ||
return 0; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,6 @@ | |
#include <QTimeLine> | ||
|
||
#include "ActionGroup.h" | ||
#include "AudioDevice.h" | ||
#include "AudioEngine.h" | ||
#include "AutomatableSlider.h" | ||
#include "ClipView.h" | ||
|
@@ -912,7 +911,7 @@ ComboBoxModel *SongEditor::snappingModel() const | |
|
||
|
||
SongEditorWindow::SongEditorWindow(Song* song) : | ||
Editor(Engine::audioEngine()->audioDev()->supportsCapture(), false), | ||
Editor(true, false), | ||
m_editor(new SongEditor(song)), | ||
m_crtlAction( nullptr ), | ||
m_snapSizeLabel( new QLabel( m_toolBar ) ) | ||
|
@@ -1029,6 +1028,17 @@ SongEditorWindow::SongEditorWindow(Song* song) : | |
|
||
connect(song, SIGNAL(projectLoaded()), this, SLOT(adjustUiAfterProjectLoad())); | ||
connect(this, SIGNAL(resized()), m_editor, SLOT(updatePositionLine())); | ||
|
||
// In case our current audio device does not support capture, | ||
// disable the record buttons. | ||
if (!Engine::audioEngine()->captureDeviceAvailable()) | ||
{ | ||
for (auto &recordAction : {m_recordAccompanyAction, m_recordAction}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was able to test with the "m_recordAccompanyAction" (I guess that's the record button with the play sign), but when I pressed Do you know why? What steps are required to make any use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this is also mentioned in #5990 . |
||
{ | ||
recordAction->setEnabled(false); | ||
recordAction->setToolTip(tr("Recording is unavailable: try connecting an input device or switching backend")); | ||
} | ||
} | ||
} | ||
|
||
QSize SongEditorWindow::sizeHint() const | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggesting to put the whole function in one line to save space (at least the coding conventions allow it).