cwFlowProc.h/cpp,cwFlow.cpp,proc_dict.cfg : Added 'audio_silence'.
Added periodic console reporting from 'audio_meter'. Added gain based termination to 'midi_voice'. Added 'test_key_pitch' to 'piano_voice'.
This commit is contained in:
parent
c1231d48ef
commit
daa4e2355c
@ -43,6 +43,7 @@ namespace cw
|
|||||||
{ "audio_duplicate", &audio_duplicate::members },
|
{ "audio_duplicate", &audio_duplicate::members },
|
||||||
{ "audio_merge", &audio_merge::members },
|
{ "audio_merge", &audio_merge::members },
|
||||||
{ "audio_mix", &audio_mix::members },
|
{ "audio_mix", &audio_mix::members },
|
||||||
|
{ "audio_silence", &audio_silence::members },
|
||||||
{ "sine_tone", &sine_tone::members },
|
{ "sine_tone", &sine_tone::members },
|
||||||
{ "pv_analysis", &pv_analysis::members },
|
{ "pv_analysis", &pv_analysis::members },
|
||||||
{ "pv_synthesis", &pv_synthesis::members },
|
{ "pv_synthesis", &pv_synthesis::members },
|
||||||
|
169
cwFlowProc.cpp
169
cwFlowProc.cpp
@ -2115,6 +2115,72 @@ namespace cw
|
|||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// audio_silence
|
||||||
|
//
|
||||||
|
namespace audio_silence
|
||||||
|
{
|
||||||
|
enum {
|
||||||
|
kSratePId,
|
||||||
|
kChCntPId,
|
||||||
|
kOutPId
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
|
||||||
|
} inst_t;
|
||||||
|
|
||||||
|
|
||||||
|
rc_t _create( proc_t* proc, inst_t* p )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
srate_t srate = 0;
|
||||||
|
unsigned ch_cnt = 1;
|
||||||
|
|
||||||
|
|
||||||
|
if((rc = var_register_and_get(proc, kAnyChIdx,
|
||||||
|
kSratePId,"srate",kBaseSfxId,srate,
|
||||||
|
kChCntPId,"ch_cnt",kBaseSfxId,ch_cnt)) != kOkRC )
|
||||||
|
{
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( srate == 0 )
|
||||||
|
srate = proc->ctx->sample_rate;
|
||||||
|
|
||||||
|
|
||||||
|
// create the output audio buffer
|
||||||
|
rc = var_register_and_set( proc, "out", kBaseSfxId, kOutPId, kAnyChIdx, srate, ch_cnt, proc->ctx->framesPerCycle );
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _destroy( proc_t* proc, inst_t* p )
|
||||||
|
{ return kOkRC; }
|
||||||
|
|
||||||
|
rc_t _value( proc_t* proc, inst_t* p, variable_t* var )
|
||||||
|
{ return kOkRC; }
|
||||||
|
|
||||||
|
rc_t _exec( proc_t* proc, inst_t* p )
|
||||||
|
{ return kOkRC; }
|
||||||
|
|
||||||
|
rc_t _report( proc_t* proc, inst_t* p )
|
||||||
|
{ return kOkRC; }
|
||||||
|
|
||||||
|
class_members_t members = {
|
||||||
|
.create = std_create<inst_t>,
|
||||||
|
.destroy = std_destroy<inst_t>,
|
||||||
|
.value = std_value<inst_t>,
|
||||||
|
.exec = std_exec<inst_t>,
|
||||||
|
.report = std_report<inst_t>
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
@ -3493,7 +3559,8 @@ namespace cw
|
|||||||
kPeakDbPId,
|
kPeakDbPId,
|
||||||
kOutPId,
|
kOutPId,
|
||||||
kPeakFlPId,
|
kPeakFlPId,
|
||||||
kClipFlPId
|
kClipFlPId,
|
||||||
|
kRptPeriodMsPId
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -3503,25 +3570,32 @@ namespace cw
|
|||||||
{
|
{
|
||||||
audio_meter_t** mtrA;
|
audio_meter_t** mtrA;
|
||||||
unsigned mtrN;
|
unsigned mtrN;
|
||||||
|
unsigned rptPeriodSmpN;
|
||||||
|
unsigned rptPhase;
|
||||||
} inst_t;
|
} inst_t;
|
||||||
|
|
||||||
|
|
||||||
rc_t create( proc_t* proc )
|
rc_t create( proc_t* proc )
|
||||||
{
|
{
|
||||||
rc_t rc = kOkRC;
|
rc_t rc = kOkRC;
|
||||||
const abuf_t* srcBuf = nullptr; //
|
const abuf_t* srcBuf = nullptr; //
|
||||||
inst_t* inst = mem::allocZ<inst_t>();
|
inst_t* inst = mem::allocZ<inst_t>();
|
||||||
|
unsigned rptPeriodMs = 0;
|
||||||
|
|
||||||
proc->userPtr = inst;
|
proc->userPtr = inst;
|
||||||
|
|
||||||
// verify that a source buffer exists
|
// verify that a source buffer exists
|
||||||
if((rc = var_register_and_get(proc, kAnyChIdx,kInPId,"in",kBaseSfxId,srcBuf )) != kOkRC )
|
if((rc = var_register_and_get(proc, kAnyChIdx,
|
||||||
|
kInPId,"in",kBaseSfxId,srcBuf,
|
||||||
|
kRptPeriodMsPId,"rpt_ms",kBaseSfxId,rptPeriodMs)) != kOkRC )
|
||||||
{
|
{
|
||||||
rc = cwLogError(rc,"The instance '%s' does not have a valid input connection.",proc->label);
|
rc = cwLogError(rc,"The instance '%s' does not have a valid input connection.",proc->label);
|
||||||
goto errLabel;
|
goto errLabel;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
inst->rptPeriodSmpN = (unsigned)(proc->ctx->sample_rate * rptPeriodMs/1000.0);
|
||||||
|
|
||||||
// allocate channel array
|
// allocate channel array
|
||||||
inst->mtrN = srcBuf->chN;
|
inst->mtrN = srcBuf->chN;
|
||||||
inst->mtrA = mem::allocZ<audio_meter_t*>( inst->mtrN );
|
inst->mtrA = mem::allocZ<audio_meter_t*>( inst->mtrN );
|
||||||
@ -3531,7 +3605,7 @@ namespace cw
|
|||||||
{
|
{
|
||||||
ftime_t wndMs;
|
ftime_t wndMs;
|
||||||
coeff_t peakThreshDb;
|
coeff_t peakThreshDb;
|
||||||
bool dbFl;
|
bool dbFl;
|
||||||
|
|
||||||
// get the audio_meter variable values
|
// get the audio_meter variable values
|
||||||
if((rc = var_register_and_get( proc, i,
|
if((rc = var_register_and_get( proc, i,
|
||||||
@ -3593,6 +3667,8 @@ namespace cw
|
|||||||
inst_t* inst = (inst_t*)proc->userPtr;
|
inst_t* inst = (inst_t*)proc->userPtr;
|
||||||
const abuf_t* srcBuf = nullptr;
|
const abuf_t* srcBuf = nullptr;
|
||||||
unsigned chN = 0;
|
unsigned chN = 0;
|
||||||
|
|
||||||
|
bool rptFl = inst->rptPeriodSmpN != 0 && inst->rptPhase >= inst->rptPeriodSmpN;
|
||||||
|
|
||||||
// get the src buffer
|
// get the src buffer
|
||||||
if((rc = var_get(proc,kInPId, kAnyChIdx, srcBuf )) != kOkRC )
|
if((rc = var_get(proc,kInPId, kAnyChIdx, srcBuf )) != kOkRC )
|
||||||
@ -3606,8 +3682,20 @@ namespace cw
|
|||||||
var_set(proc, kOutPId, i, inst->mtrA[i]->outDb );
|
var_set(proc, kOutPId, i, inst->mtrA[i]->outDb );
|
||||||
var_set(proc, kPeakFlPId, i, inst->mtrA[i]->peakFl );
|
var_set(proc, kPeakFlPId, i, inst->mtrA[i]->peakFl );
|
||||||
var_set(proc, kClipFlPId, i, inst->mtrA[i]->clipFl );
|
var_set(proc, kClipFlPId, i, inst->mtrA[i]->clipFl );
|
||||||
}
|
|
||||||
|
|
||||||
|
if( rptFl )
|
||||||
|
cwLogPrint("%6.2f ",inst->mtrA[i]->outDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rptFl)
|
||||||
|
{
|
||||||
|
cwLogPrint("\n");
|
||||||
|
inst->rptPhase -= inst->rptPeriodSmpN;
|
||||||
|
}
|
||||||
|
|
||||||
|
inst->rptPhase += srcBuf->frameN;
|
||||||
|
|
||||||
|
|
||||||
errLabel:
|
errLabel:
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@ -4187,7 +4275,12 @@ namespace cw
|
|||||||
double cur_pbend; // current pitch bend factor
|
double cur_pbend; // current pitch bend factor
|
||||||
|
|
||||||
unsigned hzN;
|
unsigned hzN;
|
||||||
double* hzA; // hzA[128] - midi to Hz lookup table.
|
double* hzA; // hzA[128] - midi to Hz lookup table.
|
||||||
|
|
||||||
|
bool done_fl;
|
||||||
|
coeff_t gain;
|
||||||
|
coeff_t gain_coeff;
|
||||||
|
coeff_t gain_thresh;
|
||||||
|
|
||||||
} inst_t;
|
} inst_t;
|
||||||
|
|
||||||
@ -4227,6 +4320,8 @@ namespace cw
|
|||||||
for(unsigned i=0; i<midi::kMidiNoteCnt; ++i)
|
for(unsigned i=0; i<midi::kMidiNoteCnt; ++i)
|
||||||
p->hzA[i] = midi_to_hz(i);
|
p->hzA[i] = midi_to_hz(i);
|
||||||
|
|
||||||
|
p->done_fl = true;
|
||||||
|
|
||||||
errLabel:
|
errLabel:
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@ -4247,6 +4342,11 @@ namespace cw
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _on_note_off( inst_t* p )
|
||||||
|
{
|
||||||
|
p->gain_coeff = 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
rc_t _exec( proc_t* proc, inst_t* p )
|
rc_t _exec( proc_t* proc, inst_t* p )
|
||||||
{
|
{
|
||||||
rc_t rc = kOkRC;
|
rc_t rc = kOkRC;
|
||||||
@ -4272,11 +4372,18 @@ namespace cw
|
|||||||
p->cur_vel = m->d1;
|
p->cur_vel = m->d1;
|
||||||
|
|
||||||
if( m->d1 == 0 )
|
if( m->d1 == 0 )
|
||||||
var_set(proc,kDoneFlPId,kAnyChIdx,true);
|
_on_note_off(p);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p->done_fl = false;
|
||||||
|
p->gain = (coeff_t)p->cur_vel / 127;
|
||||||
|
p->gain_coeff = 1.0;
|
||||||
|
p->gain_thresh = 0.001;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case midi::kNoteOffMdId:
|
case midi::kNoteOffMdId:
|
||||||
var_set(proc,kDoneFlPId,kAnyChIdx,true);
|
_on_note_off(p);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case midi::kPbendMdId:
|
case midi::kPbendMdId:
|
||||||
@ -4289,16 +4396,13 @@ namespace cw
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if the voice is off then zero the audio buffer
|
// if the voice is off then zero the audio buffer
|
||||||
if( p->cur_vel == 0 )
|
if( p->done_fl )
|
||||||
{
|
{
|
||||||
vop::zero(abuf->buf,abuf->frameN);
|
vop::zero(abuf->buf,abuf->frameN);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
// calculate the gain based on the cur_vel
|
|
||||||
coeff_t gain = (coeff_t)p->cur_vel / 127;
|
|
||||||
|
|
||||||
// fill in the audio buffer
|
// fill in the audio buffer
|
||||||
for(unsigned i=0; i<abuf->frameN; ++i)
|
for(unsigned i=0; i<abuf->frameN; ++i)
|
||||||
{
|
{
|
||||||
@ -4306,12 +4410,21 @@ namespace cw
|
|||||||
double frac = p->wtPhase - j;
|
double frac = p->wtPhase - j;
|
||||||
sample_t smp = p->wtA[j] + (p->wtA[j+1] - p->wtA[j]) * frac;
|
sample_t smp = p->wtA[j] + (p->wtA[j+1] - p->wtA[j]) * frac;
|
||||||
|
|
||||||
abuf->buf[i] = gain*smp;
|
abuf->buf[i] = p->gain*smp;
|
||||||
|
|
||||||
p->wtPhase += p->cur_hz + (p->cur_hz * p->cur_pbend);
|
p->wtPhase += p->cur_hz + (p->cur_hz * p->cur_pbend);
|
||||||
if( p->wtPhase >= p->wtN )
|
if( p->wtPhase >= p->wtN )
|
||||||
p->wtPhase -= p->wtN;
|
p->wtPhase -= p->wtN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p->gain *= p->gain_coeff;
|
||||||
|
|
||||||
|
if( p->gain < p->gain_thresh )
|
||||||
|
{
|
||||||
|
var_set(proc,kDoneFlPId,kAnyChIdx,true);
|
||||||
|
p->done_fl = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errLabel:
|
errLabel:
|
||||||
@ -4344,6 +4457,7 @@ namespace cw
|
|||||||
kOutPId,
|
kOutPId,
|
||||||
kDoneFlPId,
|
kDoneFlPId,
|
||||||
kTestPitchPId,
|
kTestPitchPId,
|
||||||
|
kKeyPitchPId,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -4374,7 +4488,8 @@ namespace cw
|
|||||||
coeff_t gain_coeff;
|
coeff_t gain_coeff;
|
||||||
bool done_fl;
|
bool done_fl;
|
||||||
|
|
||||||
unsigned test_pitch; // Base test pitch
|
unsigned test_pitch; // Pitch under test or 0 if not on test mode
|
||||||
|
unsigned test_key_pitch; // Key associated with lowest velocity when in test mode.
|
||||||
unsigned test_pitchN; // Count of valid velocities for test_pitch
|
unsigned test_pitchN; // Count of valid velocities for test_pitch
|
||||||
unsigned* test_pitch_map; // test_pitch_map[ test_pitch_N ]
|
unsigned* test_pitch_map; // test_pitch_map[ test_pitch_N ]
|
||||||
|
|
||||||
@ -4395,6 +4510,7 @@ namespace cw
|
|||||||
{
|
{
|
||||||
assert( j < p->test_pitchN );
|
assert( j < p->test_pitchN );
|
||||||
p->test_pitch_map[j++] = i;
|
p->test_pitch_map[j++] = i;
|
||||||
|
//printf("%i %i %i\n",j-1,i,p->test_pitchN);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -4417,7 +4533,8 @@ namespace cw
|
|||||||
kWtbInstrPId, "wtb_instr", kBaseSfxId, wtb_instr,
|
kWtbInstrPId, "wtb_instr", kBaseSfxId, wtb_instr,
|
||||||
kInPId, "in", kBaseSfxId, mbuf,
|
kInPId, "in", kBaseSfxId, mbuf,
|
||||||
kDoneFlPId, "done_fl", kBaseSfxId, done_fl,
|
kDoneFlPId, "done_fl", kBaseSfxId, done_fl,
|
||||||
kTestPitchPId, "test_pitch",kBaseSfxId, p->test_pitch)) != kOkRC )
|
kTestPitchPId, "test_pitch",kBaseSfxId, p->test_pitch,
|
||||||
|
kKeyPitchPId, "test_key_pitch", kBaseSfxId, p->test_key_pitch)) != kOkRC )
|
||||||
{
|
{
|
||||||
goto errLabel;
|
goto errLabel;
|
||||||
}
|
}
|
||||||
@ -4536,8 +4653,8 @@ namespace cw
|
|||||||
// if in voice test mode
|
// if in voice test mode
|
||||||
if( p->test_pitch_map != nullptr )
|
if( p->test_pitch_map != nullptr )
|
||||||
{
|
{
|
||||||
// if the the pitch is in side the test range
|
// if the the pitch is inside the test range
|
||||||
if( d0 < p->test_pitch || p->test_pitch + p->test_pitchN <= d1 )
|
if( d0 < p->test_key_pitch || p->test_key_pitch + p->test_pitchN <= d0 )
|
||||||
goto errLabel;
|
goto errLabel;
|
||||||
|
|
||||||
// then the pitch is set to the test pitch ...
|
// then the pitch is set to the test pitch ...
|
||||||
@ -4545,7 +4662,7 @@ namespace cw
|
|||||||
|
|
||||||
// ... and the velocity is mapped to a vel for which there is a known vel in the wt-bank
|
// ... and the velocity is mapped to a vel for which there is a known vel in the wt-bank
|
||||||
// Performed pitches above the test pitch trigger increasing velocities.
|
// Performed pitches above the test pitch trigger increasing velocities.
|
||||||
d1 = p->test_pitch_map[ m->d0 - p->test_pitch ];
|
d1 = p->test_pitch_map[ m->d0 - p->test_key_pitch ];
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the wave-table associated with the pitch and velocity
|
// get the wave-table associated with the pitch and velocity
|
||||||
@ -4598,17 +4715,23 @@ namespace cw
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// for each channel
|
||||||
for(unsigned i=0; i<kChCnt; ++i)
|
for(unsigned i=0; i<kChCnt; ++i)
|
||||||
{
|
{
|
||||||
sample_t* yV = abuf->buf + i*abuf->frameN;
|
// for the output buffer
|
||||||
|
sample_t* yV = abuf->buf + i*abuf->frameN;
|
||||||
unsigned yi = 0;
|
unsigned yi = 0;
|
||||||
|
|
||||||
|
// get this channels oscillator
|
||||||
osc_state_t* osc = p->osc + i;
|
osc_state_t* osc = p->osc + i;
|
||||||
|
|
||||||
|
// for each sample in the output buffer
|
||||||
while(yi < abuf->frameN)
|
while(yi < abuf->frameN)
|
||||||
{
|
{
|
||||||
|
// locate the current wavetable
|
||||||
wt_bank::seg_t* seg = p->wt->chA[i].segA + osc->seg_idx;
|
wt_bank::seg_t* seg = p->wt->chA[i].segA + osc->seg_idx;
|
||||||
unsigned n;
|
unsigned n;
|
||||||
|
|
||||||
if( seg->aN - osc->smp_idx > abuf->frameN-yi )
|
if( seg->aN - osc->smp_idx > abuf->frameN-yi )
|
||||||
n = abuf->frameN-yi;
|
n = abuf->frameN-yi;
|
||||||
else
|
else
|
||||||
@ -4639,7 +4762,7 @@ namespace cw
|
|||||||
|
|
||||||
if( p->gain < p->kGainThreshold && !p->done_fl )
|
if( p->gain < p->kGainThreshold && !p->done_fl )
|
||||||
{
|
{
|
||||||
//printf("done\n");
|
//printf("done:\n");
|
||||||
var_set(proc,kDoneFlPId,kAnyChIdx,true);
|
var_set(proc,kDoneFlPId,kAnyChIdx,true);
|
||||||
p->done_fl = true;
|
p->done_fl = true;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ namespace cw
|
|||||||
namespace audio_duplicate { extern class_members_t members; }
|
namespace audio_duplicate { extern class_members_t members; }
|
||||||
namespace audio_mix { extern class_members_t members; }
|
namespace audio_mix { extern class_members_t members; }
|
||||||
namespace audio_marker { extern class_members_t members; }
|
namespace audio_marker { extern class_members_t members; }
|
||||||
|
namespace audio_silence { extern class_members_t members; }
|
||||||
namespace sine_tone { extern class_members_t members; }
|
namespace sine_tone { extern class_members_t members; }
|
||||||
namespace pv_analysis { extern class_members_t members; }
|
namespace pv_analysis { extern class_members_t members; }
|
||||||
namespace pv_synthesis { extern class_members_t members; }
|
namespace pv_synthesis { extern class_members_t members; }
|
||||||
|
@ -130,6 +130,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audio_silence: {
|
||||||
|
vars: {
|
||||||
|
srate: { type:srate, value:0, flags:["init"], doc:"Signal sample rate. 0=Use default system sample rate"},
|
||||||
|
ch_cnt: { type:uint, value:1, flags:["init"], doc:"Count of output audio channels. (e.g. 1=mono, 2=stereo, ...)"},
|
||||||
|
out: { type:audio, doc:"Audio signal containing only 0."},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
sine_tone: {
|
sine_tone: {
|
||||||
vars: {
|
vars: {
|
||||||
@ -545,7 +553,8 @@
|
|||||||
peakDb: { type:coeff, value: -10.0, doc:"Peak threshold." },
|
peakDb: { type:coeff, value: -10.0, doc:"Peak threshold." },
|
||||||
out: { type:coeff, value: 0.0, doc:"Meter output." },
|
out: { type:coeff, value: 0.0, doc:"Meter output." },
|
||||||
peakFl: { type:bool, value: false, doc:"Peak output." }
|
peakFl: { type:bool, value: false, doc:"Peak output." }
|
||||||
clipFl: { type:bool, value: false, doc:"Clip indicator output."}
|
clipFl: { type:bool, value: false, doc:"Clip indicator output."},
|
||||||
|
rpt_ms: { type:uint, value:0, flags:["init"], doc:"Report period in ms or 0 for no report."},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -715,7 +724,8 @@
|
|||||||
in: { type:midi, doc:"MIDI in" },
|
in: { type:midi, doc:"MIDI in" },
|
||||||
out: { type:audio, doc:"Audio out" },
|
out: { type:audio, doc:"Audio out" },
|
||||||
done_fl: { type:bool, value:false, doc:"Triggers when voice is available."},
|
done_fl: { type:bool, value:false, doc:"Triggers when voice is available."},
|
||||||
test_pitch: { type:uint, value:0, doc:"Base testing pitch." },
|
test_pitch: { type:uint, value:0, doc:"Pitch to test." },
|
||||||
|
test_key_pitch: { type:uint, value:48, doc:"Base pitch to use for lowest velocity when in 'test' mode." },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user