Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
166 commits
Select commit Hold shift + click to select a range
dda8799
Change to callback API
sakertooth Apr 22, 2025
05f7e39
Use SampleFrame abstraction in AudioResampler
sakertooth Apr 22, 2025
eded00a
Remove advance call
sakertooth Apr 22, 2025
bf219e0
Simplfy AudioResampler API, handle incomplete reads of write buffer
sakertooth Apr 22, 2025
a651add
Enforce a channel count of 2
sakertooth Apr 22, 2025
e897e94
Fix calculation of resample ratio
sakertooth Apr 22, 2025
36a561d
Removed unused include
sakertooth Apr 22, 2025
fb7dbac
Fix freeze when reaching the end of sample
sakertooth Apr 22, 2025
9c2a999
Persist write buffer across resample calls
sakertooth Apr 22, 2025
2ab0d8a
Not sure I want do to that here, maybe in another PR
sakertooth Apr 22, 2025
74cd449
Update copyright
sakertooth Apr 22, 2025
33bf943
Allow for AudioResampler to be moved, add more docs
sakertooth Apr 22, 2025
afeb45d
Remove bounds check in Sample::play
sakertooth Apr 22, 2025
62adb98
Return true or false from resample call
sakertooth Apr 23, 2025
5ca52ab
Fix signedness issues and others
sakertooth Apr 23, 2025
e7d4c51
Make resampler API more flexible
sakertooth Apr 24, 2025
f7abcf8
Add BipBuffer
sakertooth Apr 25, 2025
0ccac34
Use BipBuffer
sakertooth Apr 25, 2025
f76a7c8
Fix BipBuffer issues
sakertooth Apr 26, 2025
127f511
Make BipBuffer atomic
sakertooth Apr 26, 2025
c29a96c
Keep it simple
sakertooth Apr 27, 2025
0ede71a
Oops
sakertooth Apr 27, 2025
c139088
Simplify some stuff
sakertooth Apr 27, 2025
139155e
Pass in ratio directly
sakertooth Apr 27, 2025
8957bd4
Use Sample for playback in SlicerT
sakertooth Apr 27, 2025
1987df3
Clean up some stuff in Sample
sakertooth Apr 27, 2025
c6a683e
Remove TODO comment
sakertooth Apr 27, 2025
8d6f2e3
Fix Patman build
sakertooth Apr 27, 2025
e5eb92d
Handle resampling errors
sakertooth Apr 27, 2025
c806208
Use AudioResampler in Sf2Player
sakertooth Apr 27, 2025
f985136
Fix signed comparison issue
sakertooth Apr 27, 2025
126c5c7
Remove write callback
sakertooth Apr 27, 2025
a1bff38
Update docs
sakertooth Apr 27, 2025
d248adb
Fix SlicerT crash
sakertooth Apr 27, 2025
5ed9184
Revert "Remove write callback"
sakertooth Apr 28, 2025
27f8473
Redesign resampler interface again
sakertooth Apr 28, 2025
54072c0
Enforce that destination buffer must hold all of source buffer when r…
sakertooth Apr 28, 2025
fe9cd1c
Add WriteCallbackResult, apply fixes
sakertooth Apr 28, 2025
e58b72a
Fix API issues and bugs
sakertooth Apr 29, 2025
b5160f9
Use AudioResampler in Watsyn
sakertooth Apr 29, 2025
7993dd0
Use AudioResampler in GigPlayer
sakertooth Apr 29, 2025
040a01f
Use AudioResampler in LadspaEffect
sakertooth Apr 29, 2025
e8ff4f1
Remove use of buffer margins
sakertooth Apr 29, 2025
33df5be
Include array header
sakertooth Apr 29, 2025
b2ad621
Enforce that the destination buffer contains all resampled audio
sakertooth Apr 29, 2025
7edbba6
Merge remote-tracking branch 'upstream/master' into fix-resampling
sakertooth May 2, 2025
06b5306
Merge remote-tracking branch 'upstream' into fix-resampling
sakertooth May 2, 2025
f7de794
Simplify interface
sakertooth May 2, 2025
d59f094
Use correct type
sakertooth May 2, 2025
64d74d0
Fix subtle issues
sakertooth May 2, 2025
a791218
Merge remote-tracking branch 'upstream' into fix-resampling
sakertooth May 4, 2025
2e6d44e
Return resampling results
sakertooth May 9, 2025
013f59b
Revert "Return resampling results"
sakertooth May 9, 2025
bfae743
Return resampling results
sakertooth May 9, 2025
f717272
Fix GigPlayer
sakertooth May 10, 2025
fe3b5df
Scope write callback lambdas
sakertooth May 10, 2025
1057df5
Remove TODO comment
sakertooth May 10, 2025
1165287
Revert "Return resampling results"
sakertooth May 10, 2025
de1c2a5
Use write callback in Watsyn
sakertooth May 10, 2025
a01e113
Always use the write callback approach
sakertooth May 10, 2025
834c3e7
Stop trying to resample if no input frames were written
sakertooth May 10, 2025
467fd27
Dont try to resample within the effect chain for LADSPA
sakertooth May 10, 2025
6d21482
Remove resampler in LADSPA
sakertooth May 10, 2025
3dbc9be
Remove unused errorDescription function
sakertooth May 11, 2025
72598fc
Allocate internal callback buffer on the heap
sakertooth May 11, 2025
a9fdaf0
Remove channel parameter in write callback
sakertooth May 12, 2025
81f488d
Rename resampleCallback to inputCallback
sakertooth May 27, 2025
cda43b9
Return bool to signify if the destination buffer is completely filled
sakertooth May 27, 2025
0b24801
Fill remaining buffer with silence when no more input frames can be g…
sakertooth May 27, 2025
0c25424
Merge remote-tracking branch 'upstream' into fix-resampling
sakertooth May 30, 2025
ca8bdc9
Improve AudioResampler API
sakertooth May 31, 2025
a853777
Replace conditional with assert, check for error on calls to fluid_sy…
sakertooth May 31, 2025
7989165
Fix bug with GigPlayer
sakertooth May 31, 2025
05e8213
Remove AudioResampler::Result, return only what is necessary
sakertooth May 31, 2025
e13b457
Enforce proper channel counts
sakertooth May 31, 2025
1ecee14
Fix signedness comparison
sakertooth May 31, 2025
a99a8d6
Fix Sample playback start position bug
sakertooth May 31, 2025
e58889f
Fix signed potential loss of data warning
sakertooth May 31, 2025
6227be8
Go back to using conditional in Sf2Player
sakertooth May 31, 2025
67918ee
Use generic InterleavedAudioBufferView struct
sakertooth Jun 1, 2025
c2e3765
Add some documentation
sakertooth Jun 1, 2025
bfb7c82
Fix GigPlayer crash/audio glitch
sakertooth Jun 1, 2025
43e4228
Fix Sf2Player crash/audio glitch
sakertooth Jun 1, 2025
b5b3654
Merge remote-tracking branch 'upstream' into fix-resampling
sakertooth Jun 6, 2025
6c17316
Use enum class for interpolation modes
sakertooth Jun 10, 2025
c125c0f
Merge remote-tracking branch 'upstream' into fix-resampling
sakertooth Jun 10, 2025
3522954
Use messmerd's audio buffer view patch
sakertooth Jun 10, 2025
06435e6
Use types from LmmsTypes
sakertooth Jun 10, 2025
8d6e78a
Add LMMS_EXPORT to Mode
sakertooth Jun 10, 2025
c09f8d9
Set item data instead of using current index (export interpolation co…
sakertooth Jun 11, 2025
1671d8b
Simplify AudioResampler interface, keep it more bare bones, dont allo…
sakertooth Jun 13, 2025
a909f69
Update documentation
sakertooth Jun 13, 2025
337e18a
Assign to freq_factor
sakertooth Jun 13, 2025
24de727
Fix GigPlayer
sakertooth Jun 13, 2025
72bf896
Apply sample rate conversion in Sample
sakertooth Jun 13, 2025
143822a
Cast to double
sakertooth Jun 13, 2025
bc7432b
Use aggregate initialization
sakertooth Jun 13, 2025
5770b7c
Remove LMMS_EXPORT from AudioResampler::Mode (ignored?)
sakertooth Jun 13, 2025
659d7a4
Make numFramesMixed of type f_cnt_t
sakertooth Jun 13, 2025
da72c85
Delete copy constructors again to fix builds
sakertooth Jun 30, 2025
8e42f7c
Remove m_interpolation variable
sakertooth Jun 30, 2025
c49a2a4
Remove unused include
sakertooth Jun 30, 2025
c2bed13
Remove end of input flag
sakertooth Jul 2, 2025
a2a67e0
Advance on calls to process, remove advance functions
sakertooth Jul 2, 2025
81b95d1
Add function overload for setting the resampling ratio with input and…
sakertooth Jul 3, 2025
1cdee38
Do not have input and outputs in getters and setters
sakertooth Jul 3, 2025
d30c0a4
Implement AudioResamplerStream
sakertooth Jul 3, 2025
4a0d3f9
Advance Gig playback stream when resampling
sakertooth Jul 3, 2025
5ae8b53
Remove max sample rate logic from LADSPA
sakertooth Jul 3, 2025
9e0ba4b
Create new resampler when reloading Sf2 instrument
sakertooth Jul 3, 2025
332f67a
Try to fix Mac builds
sakertooth Jul 3, 2025
c532eb0
Dont create resampler when reloading Sf2, performance issue
sakertooth Jul 3, 2025
2d6fef4
Add constructor to qualitySettings
sakertooth Jul 3, 2025
a88942e
And new line at the end of AudioResamplerStream.h
sakertooth Jul 4, 2025
6c81eb2
Add reset function
sakertooth Jul 5, 2025
b5fbee3
Reset resmplaer state in Watsyn on calls to srccpy
sakertooth Jul 5, 2025
75630cc
Simplify source index calculation in Watsyn
sakertooth Jul 5, 2025
529bf3f
Add flush function, remove endOfInput flag
sakertooth Jul 5, 2025
c561e8d
Remove flush function
sakertooth Jul 5, 2025
e0310fa
Move AudioResamplerSteam to AudioResampler::Stream
sakertooth Jul 9, 2025
3ddfdf2
Take into account the frequency when resampling in Sample
sakertooth Jul 10, 2025
6a026af
Rewrite resampling loop to fix bugs
sakertooth Jul 14, 2025
007eddf
Add subspan function for interleaved views
sakertooth Jul 14, 2025
94dd106
Update subspan function
sakertooth Jul 16, 2025
69863ab
Merge AudioResampler::Stream functionality into AudioResampler
sakertooth Jul 19, 2025
826693d
Spruce up documentation a bit
sakertooth Jul 19, 2025
3927b17
Make AudioResampler stateless again
sakertooth Jul 19, 2025
fb2fa16
Stop resampling loop if no input data
sakertooth Jul 19, 2025
883ed0d
Add TODO comments
sakertooth Jul 20, 2025
d43d09a
Merge remote-tracking branch 'upstream' into fix-resampling
sakertooth Jul 29, 2025
7e4230f
Merge remote-tracking branch 'upstream' into fix-resampling
sakertooth Aug 11, 2025
841b78d
Use emplace_back
sakertooth Aug 11, 2025
2805834
Remove my subspan function
sakertooth Aug 11, 2025
44b9011
Simplify Sf2 input handling
sakertooth Aug 11, 2025
b906420
Use std::vector instead of QList for storing Gig samples
sakertooth Aug 11, 2025
84d455d
Use only one call to setRatio in Sample
sakertooth Aug 11, 2025
c7d8448
Handle m_ratio and m_error in move constructor and move assignment op…
sakertooth Aug 11, 2025
660cbe1
Use type erasure to make libsamplerate an implementation detail
sakertooth Aug 12, 2025
d79c6a2
Store std::unique_ptr, remove the user-defined special member functions
sakertooth Aug 12, 2025
e95da7f
Restructure audio pipeline in GigPlayer a bit
sakertooth Aug 14, 2025
1c5d41c
Simplify mix loop
sakertooth Aug 14, 2025
6303c52
Maybe will fix MinGW (??)
sakertooth Aug 14, 2025
18330a0
Consistency for now
sakertooth Aug 15, 2025
29501e3
Use brace initialization
sakertooth Aug 19, 2025
3920872
Add BipBuffer
sakertooth Aug 24, 2025
c22e3bc
Default BipBuffer capacity to DEFAULT_BUFFER_SIZE
sakertooth Aug 25, 2025
e577301
Add empty/full functions, update docs
sakertooth Aug 25, 2025
0008801
Revert use of std::clamp to if condition
sakertooth Aug 25, 2025
a1b9617
Update BipBuffer implementation
sakertooth Aug 27, 2025
e6b8c06
Use BipBuffer
sakertooth Aug 31, 2025
8d26258
Add safeguards in BipBuffer
sakertooth Sep 3, 2025
e23549d
Revert "Add safeguards in BipBuffer"
sakertooth Sep 3, 2025
5a0b737
Rename BipBuffer functions
sakertooth Sep 7, 2025
b343ac4
Remove quality settings (interpolation mode)
sakertooth Sep 12, 2025
0e5c462
Fix a few things
sakertooth Sep 12, 2025
102c45f
Only set interpolation mode in Sf2 if sample rates differ
sakertooth Sep 13, 2025
0a496c5
Move converterType function into anonymous namespace
sakertooth Oct 5, 2025
7c3f4ad
Remove BipBuffer
sakertooth Oct 10, 2025
5d8d212
Fix signed comparison issue
sakertooth Oct 10, 2025
ec4fc4c
Use std::span
sakertooth Oct 11, 2025
6334d2d
Update copyright
sakertooth Oct 19, 2025
caf0e09
Remove redundant assignment to nullptr
sakertooth Oct 19, 2025
a5b077a
Use m_ prefix
sakertooth Oct 19, 2025
4bd8098
Remove defaulted Sample destructor
sakertooth Oct 21, 2025
ae03647
Do not pass detuning info for frame index in Patman's playback state
sakertooth Oct 21, 2025
6cefb75
Use nested anonymous namespace
sakertooth Oct 21, 2025
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
49 changes: 1 addition & 48 deletions include/AudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,40 +105,6 @@ class LMMS_EXPORT AudioEngine : public QObject
AudioEngine* m_audioEngine;
};

struct qualitySettings
{
enum class Interpolation
{
Linear,
SincFastest,
SincMedium,
SincBest
} ;

Interpolation interpolation;

qualitySettings(Interpolation i) :
interpolation(i)
{
}

int libsrcInterpolation() const
{
switch( interpolation )
{
case Interpolation::Linear:
return SRC_ZERO_ORDER_HOLD;
case Interpolation::SincFastest:
return SRC_SINC_FASTEST;
case Interpolation::SincMedium:
return SRC_SINC_MEDIUM_QUALITY;
case Interpolation::SincBest:
return SRC_SINC_BEST_QUALITY;
}
return SRC_LINEAR;
}
} ;

void initDevices();
void clear();
void clearNewPlayHandles();
Expand All @@ -160,10 +126,7 @@ class LMMS_EXPORT AudioEngine : public QObject

//! Set new audio device. Old device will be deleted,
//! unless it's stored using storeAudioDevice
void setAudioDevice( AudioDevice * _dev,
const struct qualitySettings & _qs,
bool _needs_fifo,
bool startNow );
void setAudioDevice(AudioDevice* _dev, bool _needs_fifo, bool startNow);
void storeAudioDevice();
void restoreAudioDevice();
inline AudioDevice * audioDev()
Expand Down Expand Up @@ -230,12 +193,6 @@ class LMMS_EXPORT AudioEngine : public QObject
return m_profiler.detailLoad(type);
}

const qualitySettings & currentQualitySettings() const
{
return m_qualitySettings;
}


sample_rate_t baseSampleRate() const { return m_baseSampleRate; }


Expand Down Expand Up @@ -300,8 +257,6 @@ class LMMS_EXPORT AudioEngine : public QObject
return hasFifoWriter() ? m_fifo->read() : renderNextBuffer();
}

void changeQuality(const struct qualitySettings & qs);

//! Block until a change in model can be done (i.e. wait for audio thread)
void requestChangeInModel();
void doneChangeInModel();
Expand Down Expand Up @@ -390,8 +345,6 @@ class LMMS_EXPORT AudioEngine : public QObject
LocklessList<PlayHandle *> m_newPlayHandles;
ConstPlayHandleList m_playHandlesToRemove;


struct qualitySettings m_qualitySettings;
float m_masterGain;

// audio device stuff
Expand Down
105 changes: 84 additions & 21 deletions include/AudioResampler.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* AudioResampler.h - wrapper around libsamplerate
* AudioResampler.h
*
* Copyright (c) 2023 saker <[email protected]>
* Copyright (c) 2025 saker <[email protected]>
*
* This file is part of LMMS - https://lmms.io
*
Expand All @@ -25,41 +25,104 @@
#ifndef LMMS_AUDIO_RESAMPLER_H
#define LMMS_AUDIO_RESAMPLER_H

#include <samplerate.h>

#include <memory>
#include "AudioBufferView.h"
#include "lmms_export.h"

namespace lmms {

/**
* @class AudioResampler
* @brief A utility class for resampling interleaved audio buffers using various resampling algorithms.
*
* This class provides support for zero-order hold, linear, and several levels of sinc-based resampling.
*/
class LMMS_EXPORT AudioResampler
{
public:
struct ProcessResult
/**
* @enum Mode
* @brief Defines the resampling method to use.
*/
enum class Mode
{
ZOH, //!< Zero Order Hold (nearest-neighbor) interpolation.
Linear, //!< Linear interpolation.
SincFastest, //!< Fastest sinc-based resampling.
SincMedium, //!< Medium quality sinc-based resampling.
SincBest //!< Highest quality sinc-based resampling.
};

/**
* @struct Result
* @brief Result of a resampling operation.
*/
struct Result
{
int error;
long inputFramesUsed;
long outputFramesGenerated;
f_cnt_t inputFramesUsed; //!< The number of input frames used during processing.
f_cnt_t outputFramesGenerated; //!< The number of output frames generated during processing.
};

AudioResampler(int interpolationMode, int channels);
AudioResampler(const AudioResampler&) = delete;
AudioResampler(AudioResampler&&) = delete;
~AudioResampler();
/**
* @brief Constructs an `AudioResampler` instance.
* @param mode The resampling mode to use.
* @param channels Number of audio channels. Defaults to `2` (stereo).
*/
AudioResampler(Mode mode, ch_cnt_t channels = 2);

AudioResampler& operator=(const AudioResampler&) = delete;
AudioResampler& operator=(AudioResampler&&) = delete;
/**
* @brief Process a block of interleaved audio input from `input` and resample it into `output`.
*
* @param input The interleaved audio input.
* @param output The interleaved audio output.
*
* @throws `std::invalid_argument` if a channel mismatch has been detected.
* @throws `std::runtime_error` if the resampling process has failed.
*
* @remark This utility class does not cache the input and output buffers, making it stateless. In other words,
* `input` is directly resampled into the `output`.
*
* @returns the result of the resampling process. See @ref Result for more details.
*/
[[nodiscard]] auto process(InterleavedBufferView<const float> input, InterleavedBufferView<float> output) -> Result;

auto resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio) -> ProcessResult;
auto interpolationMode() const -> int { return m_interpolationMode; }
auto channels() const -> int { return m_channels; }
void setRatio(double ratio);
/**
* @brief Resets the internal resampler state.
* Useful when working with unreleated pieces of audio.
*/
void reset();

/**
* @brief Sets the resampling ratio to `ratio`.
* @param ratio Output sample rate divided by input sample rate.
*/
void setRatio(double ratio) { m_ratio = ratio; }

/**
* @brief Sets the resampling ratio to `output / input`.
* @param input Input sample rate.
* @param output Output sample rate.
*/
void setRatio(sample_rate_t input, sample_rate_t output) { m_ratio = static_cast<double>(output) / input; }

//! @returns the resampling ratio.
auto ratio() const -> double { return m_ratio; }

//! @returns the number of channels expected by the resampler.
auto channels() const -> ch_cnt_t { return m_channels; }

//! @returns the interpolation mode used by this resampler.
auto mode() const -> Mode { return m_mode; }

private:
int m_interpolationMode = -1;
int m_channels = 0;
struct LMMS_EXPORT StateDeleter { void operator()(void* state); };
std::unique_ptr<void, StateDeleter> m_state;
Mode m_mode;
ch_cnt_t m_channels = 0;
double m_ratio = 1.0;
int m_error = 0;
SRC_STATE* m_state = nullptr;
};

} // namespace lmms

#endif // LMMS_AUDIO_RESAMPLER_H
32 changes: 0 additions & 32 deletions include/Effect.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class LMMS_EXPORT Effect : public Plugin
Effect( const Plugin::Descriptor * _desc,
Model * _parent,
const Descriptor::SubPluginFeatures::Key * _key );
~Effect() override;

void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override;
void loadSettings( const QDomElement & _this ) override;
Expand Down Expand Up @@ -176,29 +175,6 @@ class LMMS_EXPORT Effect : public Plugin

gui::PluginView* instantiateView( QWidget * ) override;

// some effects might not be capable of higher sample-rates so they can
// sample it down before processing and back after processing
inline void sampleDown( const SampleFrame* _src_buf,
SampleFrame* _dst_buf,
sample_rate_t _dst_sr )
{
resample( 0, _src_buf,
Engine::audioEngine()->outputSampleRate(),
_dst_buf, _dst_sr,
Engine::audioEngine()->framesPerPeriod() );
}

inline void sampleBack( const SampleFrame* _src_buf,
SampleFrame* _dst_buf,
sample_rate_t _src_sr )
{
resample( 1, _src_buf, _src_sr, _dst_buf,
Engine::audioEngine()->outputSampleRate(),
Engine::audioEngine()->framesPerPeriod() * _src_sr /
Engine::audioEngine()->outputSampleRate() );
}
void reinitSRC();

virtual void onEnabledChanged() {}


Expand All @@ -212,10 +188,6 @@ class LMMS_EXPORT Effect : public Plugin


EffectChain * m_parent;
void resample( int _i, const SampleFrame* _src_buf,
sample_rate_t _src_sr,
SampleFrame* _dst_buf, sample_rate_t _dst_sr,
const f_cnt_t _frames );

bool m_okay;
bool m_noRun;
Expand All @@ -230,10 +202,6 @@ class LMMS_EXPORT Effect : public Plugin

bool m_autoQuitEnabled = false;

SRC_DATA m_srcData[2];
SRC_STATE * m_srcState[2];


friend class gui::EffectView;
friend class EffectChain;

Expand Down
7 changes: 1 addition & 6 deletions include/ProjectRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,7 @@ class LMMS_EXPORT ProjectRenderer : public QThread
AudioFileDeviceInstantiaton m_getDevInst;
} ;


ProjectRenderer( const AudioEngine::qualitySettings & _qs,
const OutputSettings & _os,
ExportFileFormat _file_format,
const QString & _out_file );
ProjectRenderer(const OutputSettings& _os, ExportFileFormat _file_format, const QString& _out_file);
~ProjectRenderer() override = default;

bool isReady() const
Expand Down Expand Up @@ -93,7 +89,6 @@ public slots:
void run() override;

AudioFileDevice * m_fileDev;
AudioEngine::qualitySettings m_qualitySettings;

volatile int m_progress;
volatile bool m_abort;
Expand Down
8 changes: 1 addition & 7 deletions include/RenderManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,7 @@ class RenderManager : public QObject
{
Q_OBJECT
public:
RenderManager(
const AudioEngine::qualitySettings & qualitySettings,
const OutputSettings & outputSettings,
ProjectRenderer::ExportFileFormat fmt,
QString outputPath);
RenderManager(const OutputSettings& outputSettings, ProjectRenderer::ExportFileFormat fmt, QString outputPath);

~RenderManager() override;

Expand All @@ -70,8 +66,6 @@ private slots:

void render( QString outputPath );

const AudioEngine::qualitySettings m_qualitySettings;
const AudioEngine::qualitySettings m_oldQualitySettings;
const OutputSettings m_outputSettings;
ProjectRenderer::ExportFileFormat m_format;
QString m_outputPath;
Expand Down
Loading
Loading