Last changes: trying to integrate Pmini
This commit is contained in:
@ -10,6 +10,7 @@
|
||||
schelp: "BuboQuark",
|
||||
dependencies: [
|
||||
"https://github.com/cappelnord/BenoitLib",
|
||||
"https://github.com/j0py/Pmini",
|
||||
"https://github.com/scztt/Singleton.quark",
|
||||
"https://github.com/scztt/Require.quark",
|
||||
"https://github.com/dmorgan-github/Pdv",
|
||||
|
||||
@ -7,7 +7,8 @@ Boot {
|
||||
|
||||
*new {
|
||||
arg configPath, samplePath, serverOptions;
|
||||
var p; var c; var t; var s; var d; var e;
|
||||
var p; var c; var t; var s; var d; var e; var b;
|
||||
Server.killAll;
|
||||
BuboUtils.fancyPrint(BuboUtils.banner, 40);
|
||||
MIDIClient.init;
|
||||
|
||||
@ -43,30 +44,34 @@ Boot {
|
||||
this.localPath = this.class.filenameSymbol.asString.dirname +/+ "Configuration";
|
||||
|
||||
p = ProxySpace.push(s.boot, clock: this.clock);
|
||||
p.quant = 4; p.fadeTime = 0.01;
|
||||
this.samplePath = samplePath ? "/Users/bubo/.config/livecoding/samples";
|
||||
|
||||
// Setting up the audio samples/buffers manager
|
||||
Bank.lazyLoading = true;
|
||||
Bank.root = this.samplePath;
|
||||
|
||||
|
||||
// Post actions: installing behavior after server boot
|
||||
Server.default.waitForBoot({
|
||||
// d = ();
|
||||
// // Exceptional Dual Sardine Boot
|
||||
// d.dirt = SuperDirt(2, s);
|
||||
// d.dirt.fileExtensions = ["wav","aif","aiff","aifc","mp3"];
|
||||
// d.dirt.loadSoundFiles("/Users/bubo/Library/Application\ Support/Sardine/SON/*");
|
||||
// d.dirt.loadSoundFiles("/Users/bubo/.config/livecoding/samples/*");
|
||||
// d.dirt.doNotReadYet = true;
|
||||
// d.dirt.start(57120, [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]);
|
||||
// (
|
||||
// d.d1 = d.dirt.orbits[0]; d.d2 = d.dirt.orbits[1]; d.d3 = d.dirt.orbits[2];
|
||||
// d.d4 = d.dirt.orbits[3]; d.d5 = d.dirt.orbits[4]; d.d6 = d.dirt.orbits[5];
|
||||
// d.d7 = d.dirt.orbits[6]; d.d8 = d.dirt.orbits[7]; d.d9 = d.dirt.orbits[8];
|
||||
// d.d10 = d.dirt.orbits[9]; d.d11 = d.dirt.orbits[10]; d.d12 = d.dirt.orbits[11];
|
||||
// );
|
||||
// d.dirt.soundLibrary.addMIDI(\midi, MIDIOut.newByName("MIDI", "Bus 1"));
|
||||
// d.dirt.soundLibrary.addMIDI(\midi2, MIDIOut.newByName("MIDI", "Bus 2"));
|
||||
if (false) {
|
||||
d = ();
|
||||
// Exceptional Dual Sardine Boot
|
||||
d.dirt = SuperDirt(2, s);
|
||||
d.dirt.fileExtensions = ["wav","aif","aiff","aifc","mp3"];
|
||||
d.dirt.loadSoundFiles("/Users/bubo/Library/Application\ Support/Sardine/SON/*");
|
||||
d.dirt.loadSoundFiles("/Users/bubo/.config/livecoding/samples/*");
|
||||
d.dirt.doNotReadYet = true;
|
||||
d.dirt.start(57120, [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]);
|
||||
(
|
||||
d.d1 = d.dirt.orbits[0]; d.d2 = d.dirt.orbits[1]; d.d3 = d.dirt.orbits[2];
|
||||
d.d4 = d.dirt.orbits[3]; d.d5 = d.dirt.orbits[4]; d.d6 = d.dirt.orbits[5];
|
||||
d.d7 = d.dirt.orbits[6]; d.d8 = d.dirt.orbits[7]; d.d9 = d.dirt.orbits[8];
|
||||
d.d10 = d.dirt.orbits[9]; d.d11 = d.dirt.orbits[10]; d.d12 = d.dirt.orbits[11];
|
||||
);
|
||||
d.dirt.soundLibrary.addMIDI(\midi, MIDIOut.newByName("MIDI", "Bus 1"));
|
||||
d.dirt.soundLibrary.addMIDI(\midi2, MIDIOut.newByName("MIDI", "Bus 2"));
|
||||
};
|
||||
s.latency = 0.3;
|
||||
|
||||
// Resume normal boot sequence
|
||||
@ -80,6 +85,9 @@ Boot {
|
||||
Safety.setLimit(1);
|
||||
e = currentEnvironment;
|
||||
|
||||
// Setting up the archive
|
||||
"Retrieving code archives".postln;
|
||||
b = Archive.global.at(\bubo);
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
+ NodeProxy {
|
||||
|
||||
/* Simple FX chain management */
|
||||
@ -10,15 +11,15 @@
|
||||
^this;
|
||||
}
|
||||
|
||||
fx1 { arg wet, function; this.fx(100, wet, function); }
|
||||
fx2 { arg wet, function; this.fx(200, wet, function); }
|
||||
fx3 { arg wet, function; this.fx(300, wet, function); }
|
||||
fx4 { arg wet, function; this.fx(400, wet, function); }
|
||||
fx5 { arg wet, function; this.fx(500, wet, function); }
|
||||
fx6 { arg wet, function; this.fx(600, wet, function); }
|
||||
fx7 { arg wet, function; this.fx(700, wet, function); }
|
||||
fx8 { arg wet, function; this.fx(800, wet, function); }
|
||||
fx9 { arg wet, function; this.fx(900, wet, function); }
|
||||
fx1 { arg wet=0.5, function; this.fx(100, wet, function); }
|
||||
fx2 { arg wet=0.5, function; this.fx(200, wet, function); }
|
||||
fx3 { arg wet=0.5, function; this.fx(300, wet, function); }
|
||||
fx4 { arg wet=0.5, function; this.fx(400, wet, function); }
|
||||
fx5 { arg wet=0.5, function; this.fx(500, wet, function); }
|
||||
fx6 { arg wet=0.5, function; this.fx(600, wet, function); }
|
||||
fx7 { arg wet=0.5, function; this.fx(700, wet, function); }
|
||||
fx8 { arg wet=0.5, function; this.fx(800, wet, function); }
|
||||
fx9 { arg wet=0.5, function; this.fx(900, wet, function); }
|
||||
|
||||
wet { arg number=1, wet=1;
|
||||
this.set(("wet" ++ number).asSymbol, wet);
|
||||
@ -37,17 +38,25 @@
|
||||
^this;
|
||||
}
|
||||
|
||||
prepareToPlay {
|
||||
| proxy, quant, fade |
|
||||
proxy.quant = quant;
|
||||
proxy.fadeTime = fade;
|
||||
// proxy.play;
|
||||
}
|
||||
|
||||
/* Syntax for sending MIDI messages */
|
||||
>> {
|
||||
arg pattern;
|
||||
var quant = this.getQuantFromPattern(pattern);
|
||||
var fade = this.getFadeFromPattern(pattern);
|
||||
pattern = EventShortener.findShortcuts(pattern);
|
||||
pattern = pattern ++ [type: 'midi'];
|
||||
pattern = EventShortener.process(
|
||||
pattern,
|
||||
this.key,
|
||||
[type: 'midi']
|
||||
);
|
||||
this[0] = Pbind(*pattern);
|
||||
this.quant = quant;
|
||||
this.fadeTime = fade;
|
||||
this.play;
|
||||
this.prepareToPlay(this, quant, fade);
|
||||
^this
|
||||
}
|
||||
|
||||
@ -56,43 +65,50 @@
|
||||
arg pattern;
|
||||
var quant = this.getQuantFromPattern(pattern);
|
||||
var fade = this.getFadeFromPattern(pattern);
|
||||
pattern = EventShortener.findShortcuts(pattern);
|
||||
pattern = pattern ++ [\type, \buboEvent];
|
||||
pattern = EventShortener.process(
|
||||
pattern,
|
||||
this.key,
|
||||
[\type, \buboEvent]
|
||||
);
|
||||
this[0] = Pbind(*pattern);
|
||||
this.quant = quant;
|
||||
this.fadeTime = fade;
|
||||
this.play;
|
||||
this.prepareToPlay(this, quant, fade);
|
||||
^this
|
||||
}
|
||||
|
||||
/* Audio Looper (sample playback) */
|
||||
== {
|
||||
// TODO: fix this terrible mess
|
||||
arg pattern;
|
||||
var quant = this.getQuantFromPattern(pattern);
|
||||
var fade = this.getFadeFromPattern(pattern);
|
||||
pattern = EventShortener.findShortcuts(pattern);
|
||||
pattern = pattern ++ [\type, \buboLoopEvent];
|
||||
pattern = pattern ++ [\legato, 1];
|
||||
pattern = pattern ++ [
|
||||
\time, Pkey(\dur) / Pfunc { currentEnvironment.clock.tempo }
|
||||
];
|
||||
this[0] = Pbind(*pattern);
|
||||
this.quant = quant;
|
||||
this.fadeTime = fade;
|
||||
this.play;
|
||||
var nbSlices = this.getValueFromPattern(pattern, 'slices', 1);
|
||||
var time = (Pkey(\dur) / Pfunc { currentEnvironment.clock.tempo }) / nbSlices;
|
||||
pattern = EventShortener.process(
|
||||
pattern,
|
||||
this.key,
|
||||
[
|
||||
\type, \buboLoopEvent,
|
||||
\legato, 1,
|
||||
\time, time
|
||||
]
|
||||
);
|
||||
this[0] = Pmono(*pattern);
|
||||
this.prepareToPlay(this, quant, fade);
|
||||
^this
|
||||
}
|
||||
|
||||
/* FIX: Rewrite this part, slightly broken */
|
||||
/* Pmono player */
|
||||
-> {
|
||||
arg pattern;
|
||||
var quant = this.getQuantFromPattern(pattern);
|
||||
var fade = this.getFadeFromPattern(pattern);
|
||||
pattern = EventShortener.findShortcuts(pattern);
|
||||
pattern = EventShortener.processPmono(
|
||||
pattern,
|
||||
this.key
|
||||
);
|
||||
this[0] = Pmono(*pattern);
|
||||
this.quant = quant;
|
||||
this.fadeTime = fade;
|
||||
this.play;
|
||||
this.prepareToPlay(this; quant, fade);
|
||||
^this
|
||||
}
|
||||
|
||||
f {
|
||||
@ -121,24 +137,25 @@
|
||||
^this
|
||||
}
|
||||
|
||||
getQuantFromPattern {
|
||||
arg pattern; var quant;
|
||||
var quantIndex = pattern.indexOf('quant');
|
||||
if (quantIndex.notNil) {
|
||||
^pattern[quantIndex + 1]
|
||||
getValueFromPattern {
|
||||
arg pattern, key, default;
|
||||
var keyIndex = pattern.indexOf(key);
|
||||
if (keyIndex.notNil) {
|
||||
^pattern[keyIndex + 1]
|
||||
} {
|
||||
^0
|
||||
^default
|
||||
}
|
||||
}
|
||||
|
||||
getQuantFromPattern {
|
||||
arg pattern;
|
||||
^this.getValueFromPattern(pattern, 'quant', 4)
|
||||
}
|
||||
|
||||
getFadeFromPattern {
|
||||
arg pattern; var fade;
|
||||
var fadeIndex = pattern.indexOf('fade');
|
||||
if (fadeIndex.notNil) {
|
||||
^pattern[fadeIndex + 1]
|
||||
} {
|
||||
^0.01
|
||||
}
|
||||
arg pattern;
|
||||
^this.getValueFromPattern(pattern, 'fade', 0.01)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
rate:BufRateScale.kr(buf) * (\rate.kr(1) * \freq.kr(400) / 60.midicps),
|
||||
trigger: 1, startPos:startPos,
|
||||
loop:\loop.kr(0), doneAction: 2);
|
||||
sig = sig * \amp.kr(-6.dbamp);
|
||||
sig = sig * \amp.kr(-6).dbamp;
|
||||
sig = Pan2.ar(sig * env, \pan.kr(0));
|
||||
OffsetOut.ar(out, sig)
|
||||
}).add;
|
||||
@ -46,7 +46,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
rate:BufRateScale.kr(buf) * (\rate.kr(1) * \freq.kr(400) / 60.midicps),
|
||||
trigger: 1, startPos:startPos,
|
||||
loop:\loop.kr(0), doneAction: 2);
|
||||
sig = sig * \amp.kr(-6.dbamp);
|
||||
sig = sig * \amp.kr(-6).dbamp;
|
||||
sig = Pan2.ar(sig * env, \pan.kr(0));
|
||||
OffsetOut.ar(out, sig)
|
||||
}).add;
|
||||
@ -72,7 +72,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
Env.asr(0.01, 1, 0.01), \gate.kr(1), doneAction: 2
|
||||
);
|
||||
sig = sig * env;
|
||||
sig = sig * \amp.kr(-6.dbamp);
|
||||
sig = sig * \amp.kr(-6).dbamp;
|
||||
OffsetOut.ar(out,Pan2.ar(sig,\pan.kr(0)));
|
||||
}).add;
|
||||
d.looperMono = z;
|
||||
@ -96,7 +96,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
Env.asr(0.01, 1, 0.01), \gate.kr(1), doneAction: 2
|
||||
);
|
||||
sig = sig * env;
|
||||
sig = sig * \amp.kr(-6.dbamp);
|
||||
sig = sig * \amp.kr(-6).dbamp;
|
||||
OffsetOut.ar(out,Pan2.ar(sig,\pan.kr(0)));
|
||||
}).add;
|
||||
d.looperStereo = z;
|
||||
@ -118,7 +118,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
1
|
||||
);
|
||||
sig = sig * env;
|
||||
OffsetOut.ar(out, Pan2.ar(sig, \pan.kr(0), \amp.kr(-6.dbamp)));
|
||||
OffsetOut.ar(out, Pan2.ar(sig, \pan.kr(0), \amp.kr(-6).dbamp));
|
||||
}).add;
|
||||
d.sinfb = z;
|
||||
);
|
||||
@ -129,7 +129,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
var envLength = \sustain.kr(1) * (\end.kr(1) - \begin.kr(0)) / \speed.kr(1);
|
||||
var line = Line.ar(\begin.kr, \end.kr, envLength, doneAction: Done.freeSelf);
|
||||
var env = Env.asr;
|
||||
var volume = IEnvGen.ar(env, line) * \amp.kr(-6.dbamp);
|
||||
var volume = IEnvGen.ar(env, line) * \amp.kr(-6).dbamp;
|
||||
var sig;
|
||||
sig = MiOmi.ar(pit: \freq.kr(400).cpsmidi);
|
||||
OffsetOut.ar(out, Pan2.ar(sig * volume, \pan.kr(0)));
|
||||
@ -168,7 +168,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
lpg_colour: \lpgcolour.kr(0),
|
||||
);
|
||||
sig = Pan2.ar(sig[0], \pan.kr(0));
|
||||
sig = sig * env * \amp.kr(-6.dbamp);
|
||||
sig = sig * env * \amp.kr(-6).dbamp;
|
||||
OffsetOut.ar(out, sig);
|
||||
}).add;
|
||||
d.put(name, synth);
|
||||
@ -243,7 +243,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
ws: \ws.kr(0),
|
||||
bits: \bits.kr(0)
|
||||
);
|
||||
OffsetOut.ar(out, Pan2.ar(sig * env * \amp.kr(-6.dbamp), \pan.kr(0)));
|
||||
OffsetOut.ar(out, Pan2.ar(sig * env * \amp.kr(-6).dbamp, \pan.kr(0)));
|
||||
}).add;
|
||||
d.put(name, synth);
|
||||
});
|
||||
@ -264,7 +264,7 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
rate: 1
|
||||
);
|
||||
OffsetOut.ar(out,
|
||||
Pan2.ar(sig * env * \amp.kr(-6.dbamp),
|
||||
Pan2.ar(sig * env * \amp.kr(-6).dbamp,
|
||||
\pan.kr(0)
|
||||
));
|
||||
}).add;
|
||||
@ -294,18 +294,18 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); };
|
||||
|
||||
(
|
||||
z = SynthDef('kick', {
|
||||
arg out, freq, mul=512, vsweep=0.5, hold=0.25, release=0.25, amp=0.5, pan=0;
|
||||
arg out;
|
||||
var p0, p1, p, freq0, freq1, freqEnv, sig;
|
||||
p0 = 0.006699687;
|
||||
p1 = 0.00001884606;
|
||||
p = (1-vsweep)*p0 + (vsweep*p1);
|
||||
freq1 = freq;
|
||||
freq0 = freq1 * mul;
|
||||
p = (1-\vsweep.kr(0.5)) * p0 + (\vsweep.kr * p1);
|
||||
freq1 = \freq.kr(100);
|
||||
freq0 = freq1 * \mul.kr(20);
|
||||
freqEnv = EnvGen.ar(Env([0,1], [1.0], [0]));
|
||||
freqEnv = freq1 + ((freq0-freq1)/(1.0 + (freqEnv/p)));
|
||||
sig = SinOsc.ar(freqEnv);
|
||||
sig = sig * EnvGen.ar(Env([1,1,0], [hold,release], [0,0]), doneAction: Done.freeSelf) * amp;
|
||||
sig = Pan2.ar(sig, pan);
|
||||
sig = sig * EnvGen.ar(Env([1,1,0], [\hold.kr(0.25), \release.kr(0.5)], [0,0]), doneAction: Done.freeSelf) * \amp.kr(-6).dbamp;
|
||||
sig = Pan2.ar(sig, \pan.kr(0.0));
|
||||
OffsetOut.ar(out, sig);
|
||||
}).add;
|
||||
d.kick = z;
|
||||
|
||||
@ -1,5 +1,76 @@
|
||||
EventShortener {
|
||||
|
||||
*process {
|
||||
arg pattern, key, other_keys;
|
||||
if (pattern.includes('pat'), {
|
||||
pattern = this.patternize(pattern);
|
||||
});
|
||||
pattern = this.findShortcuts(pattern);
|
||||
pattern = this.functionsToNdef(pattern, key);
|
||||
pattern = pattern ++ other_keys ;
|
||||
^pattern
|
||||
}
|
||||
|
||||
*patternize {
|
||||
arg pattern;
|
||||
var delta_index = nil;
|
||||
var new_pattern = List();
|
||||
pattern.doAdjacentPairs({
|
||||
arg a, b, index;
|
||||
if (index % 2 == 0, {
|
||||
if (a === 'pat', {
|
||||
var temp = Pmini(b);
|
||||
temp.pattern.postln;
|
||||
new_pattern = new_pattern ++ [
|
||||
[\trig, \delta, \dur, \str, \num],
|
||||
temp
|
||||
];
|
||||
new_pattern = new_pattern ++ [
|
||||
degree: Pfunc({ |e|
|
||||
if (e.trig > 0, {
|
||||
e.str.asInteger
|
||||
}, {
|
||||
\rest
|
||||
}
|
||||
)});
|
||||
];
|
||||
if (pattern.includes('i') || pattern.includes('instrument') == false, {
|
||||
new_pattern = new_pattern ++ [
|
||||
sp: Pkey(\str),
|
||||
nb: Pkey(\num),
|
||||
fast: 1,
|
||||
];
|
||||
});
|
||||
}, {
|
||||
new_pattern.add(a);
|
||||
new_pattern.add(b);
|
||||
});
|
||||
})
|
||||
});
|
||||
^new_pattern
|
||||
}
|
||||
|
||||
*processPmono {
|
||||
arg pattern, key;
|
||||
pattern = this.findShortcuts(pattern);
|
||||
pattern = this.functionsToNdef(pattern, key);
|
||||
^pattern
|
||||
}
|
||||
|
||||
*functionsToNdef {
|
||||
arg pattern, key;
|
||||
var new_pattern = List.new();
|
||||
pattern.do({
|
||||
| element, i |
|
||||
if (element.isKindOf(Function), {
|
||||
new_pattern.add(Ndef((key ++ pattern[i - 1]).asSymbol, element))
|
||||
}, {
|
||||
new_pattern.add(element)
|
||||
});
|
||||
})
|
||||
^new_pattern
|
||||
}
|
||||
|
||||
*findShortcuts {
|
||||
arg pattern;
|
||||
var short, correctedPattern;
|
||||
@ -38,13 +109,13 @@ EventShortener {
|
||||
\scl, \scale,
|
||||
]);
|
||||
|
||||
// shortcuts are turned into regular keys;
|
||||
pattern.do({| element |
|
||||
pattern.do({| element, i |
|
||||
if (short.includesKey(element),
|
||||
{correctedPattern.add(short[element])},
|
||||
{correctedPattern.add(element)}
|
||||
);
|
||||
});
|
||||
|
||||
^correctedPattern;
|
||||
}
|
||||
}
|
||||
|
||||
6
Classes/PseudoUgen/Chorus.sc
Normal file
6
Classes/PseudoUgen/Chorus.sc
Normal file
@ -0,0 +1,6 @@
|
||||
Chorus {
|
||||
*ar {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
11
Classes/PseudoUgen/Compressor.sc
Normal file
11
Classes/PseudoUgen/Compressor.sc
Normal file
@ -0,0 +1,11 @@
|
||||
// TODO: broken
|
||||
Compressor {
|
||||
*ar {
|
||||
arg signal, attack, release, threshold, ratio;
|
||||
var gainDb, amplitudeDb;
|
||||
amplitudeDb = Amplitude.ar(signal, attack, release).ampdb;
|
||||
gainDb = ((amplitudeDb - threshold) * (1 / ratio - 1)).min(0);
|
||||
signal = signal * gainDb.dbamp;
|
||||
signal
|
||||
}
|
||||
}
|
||||
12
Classes/PseudoUgen/Phaser.sc
Normal file
12
Classes/PseudoUgen/Phaser.sc
Normal file
@ -0,0 +1,12 @@
|
||||
BuboPhaser {
|
||||
* ar {
|
||||
arg signal, speed=2, skew=0, feedback=0.25, mod=0.5;
|
||||
^AnalogPhaser.ar(
|
||||
input: signal,
|
||||
lfoinput: LFNoise2.ar(speed).range(-1, 1),
|
||||
skew: skew.clip2(1),
|
||||
feedback: feedback.clip2(1),
|
||||
modulation: mod.clip2(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
9
Classes/PseudoUgen/StereoWah.sc
Normal file
9
Classes/PseudoUgen/StereoWah.sc
Normal file
@ -0,0 +1,9 @@
|
||||
StereoWah {
|
||||
*ar {
|
||||
arg signal, leftSpeed, rightSpeed;
|
||||
^[
|
||||
signal[0] * SinOsc.ar(leftSpeed).range(0.1, 1),
|
||||
signal[1] * SinOsc.ar(rightSpeed).range(0.1, 1),
|
||||
]
|
||||
}
|
||||
}
|
||||
5
Classes/PseudoUgen/Wah.sc
Normal file
5
Classes/PseudoUgen/Wah.sc
Normal file
@ -0,0 +1,5 @@
|
||||
Wah {
|
||||
*ar { arg signal, speed;
|
||||
^(signal * SinOsc.ar(speed).range(0.1, 1))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user