def _partition_fib(n: int, numpartitions: int, homogeneity=0.) -> List[int]: """ if homogeneity == 0: the partitions build a fib curve, which means that if s = partition_fib(n, partitions, homogeneity=0), then all(s[i] + s[i+1] == s[i+2] for i in range(len(s-2))) is True if homogeneity == 1: the partitions build a linear curve first a fib curve is built, then an interpolation between this curve and a linear curve is done """ assert 0 <= homogeneity <= 1 if numpartitions > 60: raise ValueError("Two many partitions. Max n == 60") n0 = 10 n1 = n0 + numpartitions fib_numbers = list(map(interpol.fib, list(range(n0, n1)))) sum_fibs = sum(fib_numbers) normalized_fibs = [(x - 0) / sum_fibs * n for x in fib_numbers] avg_y = n / numpartitions partitions = [bpf.linear(0, 1, y, avg_y)(homogeneity) for y in normalized_fibs] assert all(partition >= 0 for partition in partitions) assert abs(sum(partitions) - n) < 0.0001, partitions return sorted(partitions)
def interleave(A, B, weight=0.5): """ interleave the elements of A and B weight: 0-1 0 -> first the elements of xs, then B, not interleaved 0.5 -> interleave A and B regularly 1 -> first the elements of B, then A """ if not B: return A elif not A: return B c = bpf.linear(0, len(A), 0.5, len(A)/len(B), 1, 1/len(A)) r = xr = c(weight) out = [] L = len(A)+len(B) A_index = 0 B_index = 0 while True: if r >= 1: if A_index < len(A): out.append(A[A_index]) A_index += 1 r -= 1 else: if B_index < len(B): out.append(B[B_index]) B_index += 1 r += xr if len(out) == L: break return out
def _test_distribute_weighted_streams(): A = "AAAAAAAAAAAAAAAAAAAAAAAA" C = "CCCCC" D = "DDD" streams = (A, C, D) stream_quantities = (len(A), len(C), len(D)) weight_bpfs = (bpf.linear(0, 1, 1, 1), # bpfs must be defined within the unity bpf.halfcos(0, 0, 0.5, 1, 1, 0), bpf.expon(0, 0, 1, 1, exp=3) ) distributed_frames = distribute_weighted_streams(stream_quantities, weight_bpfs) for frame in distributed_frames: print(streams[frame.stream][frame.index_in_stream])
def criticalband(freq): # type: (float) -> float """ Equivalent Rectangular Bandwidth as a function of Centre Frequency (in Hz) http://web.mit.edu/HST.723/www/ThemePapers/Masking/Moore95.pdf """ moore95 = _curves.get('moore95') if not moore95: moore95 = bpf.linear(30, 30, 100, 35, 200, 45, 300, 50, 500, 70, 1000, 110, 2000, 220, 3000, 500, 10000, 900).keep_skope() _curves['moore95'] = moore95 band = moore95(freq) # type: float return band
def plot_pyplot(partials, size=20, alpha=0.5, downsample=1, kind='amp', **kws): from matplotlib import pyplot as plt fig, ax = plt.subplots(1, 1) if kind == 'amp': for p in partials: _pyplot_plot_partial_amp(p, ax, size, alpha, downsample=downsample) elif kind == 'bw': for p in partials: _pyplot_plot_partial_bw(p, ax, size, alpha, downsample=downsample) plt.show() return True _pyplot_colorcurve_amp = bpf.linear(-90, 0.8, -30, 0.3, -6, 0.1, 0, 0) _pyplot_colorcurve_bw = bpf.linear(0, 0.15, 0.5, 0.3, 0.9, 0.5, 0.95, 0.8, 1, 0.99) def _pyplot_plot_partial_amp(partial: _partial.Partial, ax, size=20.0, alpha=0.5, downsample:int=1): X = partial.times # type: np.ndarray Y = partial.freqs # type: np.ndarray amps = partial.amps # type: np.ndarray if downsample > 1: X = X[::downsample] Y = Y[::downsample] amps = amps[::downsample] dbs = amp2db_np(amps) colorcurve = _pyplot_colorcurve_amp Z = colorcurve.map(dbs, out=dbs)
def bpfs(): y = 1.5 b = bpf4.linear(0, y, 1, y, 10, y, 10.01, y, 10.010001, y, 20, y) yield b yield bpf4.linear(-1, 10, 1, 10, 10000, 10)
class XmlNotehead(NamedTuple): shape: str filled: bool MUSICXML_NOTEHEADS = [ XmlNotehead("normal", filled=False), XmlNotehead("square", filled=False), XmlNotehead("diamond", filled=False), XmlNotehead("harmonic", filled=False), XmlNotehead("x", filled=False), XmlNotehead("circle-x", filled=False) ] # type: List[XmlNotehead] bw_to_noteheadindex = bpf.linear(0, 0, 1, len(MUSICXML_NOTEHEADS)-1) # exactitud de la grid en la traduccion a musicxml # lcm(3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 32) # No puede representar exactamente tuples de 11, 13, 17 MUSICXML_DIVISIONS = 10080 MUSICXML_TENTHS = 40 DEFAULT_POSSIBLE_DIVISIONS = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12) IRREGULAR_TUPLES = ( (5, 6), (5, 7), (5, 8), (5, 9),
endin """ return template.format(name=name, audiogen=audiogen) def _get_possible_instrs(orc=None): matches = re.findall(r"\binstr\s+\b\S+\b", _orc) return [match.split()[1] for match in matches] _possible_instrs = _get_possible_instrs() _db2vel = bpf.linear( -120, 0, -90, 10, -70, 20, -24, 90, 0, 127 ) def makeScoreLine(instr:str, start:float, dur:float, pitch1:float, pitch2:float, ampdB:float, comment:str='') -> str: commentStr = '' if not comment else f"; {comment}" return f'i "{instr}"\t{start:.6f}\t{dur:.6f}\t{pitch1}\t{pitch2}\t{ampdB}\t\t {commentStr}' def csoundScoreForPart(part: Part, defaultInstr='piano', allowDiscontinuousGliss=True, defaultGain=0.5) -> List[str]: # TODO pass
import sndtrck import bpf4 as bpf from emlib.pitchtools import * from emlib.iterlib import pairwise, flatten from emlib.distribute import dohndt from .track import Track from .config import RenderConfig from .envir import logger from .typehints import * from functools import lru_cache _default = { 'freq2points': bpf.linear(0, 0.00001, 20, 5, 50, 100, 400, 100, 800, 80, 2800, 80, 3800, 50, 4500, 10, 5000, 0), 'amp2points': bpf.linear(-90, 0.00001, -60, 2, -35, 40, -12, 95, 0, 100), 'dur2points': bpf.linear(0, 0.0001, 0.1, 5, 0.3, 20, 0.5, 40, 2, 70, 5, 100), 'harmonicity_gain': bpf.linear(0.7, 1, 1, 2), 'overtone_gain': bpf.linear(1, 1.5, 5, 1) } def _asbpf(obj): if isinstance(obj, dict): return bpf.util.dict_to_bpf(obj) elif isinstance(obj, list):
def mincer(sndfile, timecurve, pitchcurve, outfile=None, dt=0.002, lock=False, fftsize=2048, ksmps=128, debug=False): """ sndfile: the path to a soundfile timecurve: a bpf mapping time to playback time or a scalar indicating a timeratio (2 means twice as fast) 1 to leave unmodified pitchcurve: a bpf mapping x=time, y=pitchscale. or a scalar indicating a freqratio (2 means an octave higher) 1 to leave unmodified outfile: the path to a resulting outfile Returns: a dictionary with information about the process NB: if the mapped time excedes the bounds of the sndfile, silence is generated. For example, a negative time or a time exceding the duration of the sndfile NB2: the samplerate and number of channels of of the generated file matches that of the input file NB3: the resulting file is always a 32-bit float .wav file ** Example 1: stretch a soundfile 2x timecurve = bpf.linear(0, 0, totaldur*2, totaldur) outfile = mincer(sndfile, timecurve, 1) """ import bpf4 as bpf import sndfileio if outfile is None: outfile = filetools.addSuffix(sndfile, "-mincer") info = sndfileio.sndinfo(sndfile) sr = info.samplerate nchnls = info.channels pitchbpf = bpf.asbpf(pitchcurve) if isinstance(timecurve, (int, float)): t0, t1 = 0, info.duration / timecurve timebpf = bpf.linear(0, 0, t1, info.duration) elif isinstance(timecurve, bpf.core._BpfInterface): t0, t1 = timecurve.bounds() timebpf = timecurve else: raise TypeError("timecurve should be either a scalar or a bpf") assert isinstance(pitchcurve, (int, float, bpf.core._BpfInterface)) ts = np.arange(t0, t1+dt, dt) fmt = "%.12f" _, time_gen23 = tempfile.mkstemp(prefix='time-', suffix='.gen23') np.savetxt(time_gen23, timebpf.map(ts), fmt=fmt, header=str(dt), comments="") _, pitch_gen23 = tempfile.mkstemp(prefix='pitch-', suffix='.gen23') np.savetxt(pitch_gen23, pitchbpf.map(ts), fmt=fmt, header=str(dt), comments="") if outfile is None: outfile = filetools.addSuffix(sndfile, '-mincer') csd = f""" <CsoundSynthesizer> <CsOptions> -o {outfile} </CsOptions> <CsInstruments> sr = {sr} ksmps = {ksmps} nchnls = {nchnls} 0dbfs = 1.0 gi_snd ftgen 0, 0, 0, -1, "{sndfile}", 0, 0, 0 gi_time ftgen 0, 0, 0, -23, "{time_gen23}" gi_pitch ftgen 0, 0, 0, -23, "{pitch_gen23}" instr vartimepitch idt tab_i 0, gi_time ilock = {int(lock)} ifftsize = {fftsize} ikperiod = ksmps/sr isndfiledur = ftlen(gi_snd) / ftsr(gi_snd) isndchnls = ftchnls(gi_snd) ifade = ikperiod*2 inumsamps = ftlen(gi_time) it1 = (inumsamps-2) * idt ; account for idt and last value kt timeinsts aidx linseg 1, it1, inumsamps-1 at1 tablei aidx, gi_time, 0, 0, 0 kpitch tablei k(aidx), gi_pitch, 0, 0, 0 kat1 = k(at1) kgate = (kat1 >= 0 && kat1 <= isndfiledur) ? 1 : 0 agate = interp(kgate) aenv linseg 0, ifade, 1, it1 - (ifade*2), 1, ifade, 0 aenv *= agate if isndchnls == 1 then a0 mincer at1, 1, kpitch, gi_snd, ilock, ifftsize, 8 outch 1, a0*aenv else a0, a1 mincer at1, 1, kpitch, gi_snd, ilock, ifftsize, 8 outs a0*aenv, a1*aenv endif if (kt >= it1 + ikperiod) then event "i", "exit", 0.1, 1 turnoff endif endin instr exit puts "exiting!", 1 exitnow endin </CsInstruments> <CsScore> i "vartimepitch" 0 -1 f 0 36000 </CsScore> </CsoundSynthesizer> """ _, csdfile = tempfile.mkstemp(suffix=".csd") with open(csdfile, "w") as f: f.write(csd) subprocess.call(["csound", "-f", csdfile]) if not debug: os.remove(time_gen23) os.remove(pitch_gen23) os.remove(csdfile) return {'outfile': outfile, 'csdstr': csd, 'csd': csdfile}
def make_weighter(mode='speech'): if mode == 'speech': freqcurve = bpf.linear(0, 0, 50, 0, 80, 1, 200, 1, 250, 2, 4500, 2, 6000, 1, 8000, 1, 10000, 0) durcurve = bpf.linear(0, 0, 0.01, 0, 0.02, 1) ampcurve = bpf.linear(0, 0, db2amp(-75), 0, db2amp(-65), 1) elif mode == 'transcription': freqcurve = bpf.linear(0, 0, 30, 0, 50, 1, 4500, 1, 5000, 0) durcurve = bpf.linear(0, 0, 0.01, 0, 0.02, 1) ampcurve = bpf.linear(0, 0, db2amp(-70), 0, db2amp(-60), 1) elif mode == 'default': freqcurve = bpf.linear(0, 0, 30, 0, 50, 1, 8000, 1, 12000, 0.5, 16000, 0) durcurve = bpf.linear(0, 0, 0.01, 0, 0.02, 1) ampcurve = bpf.linear(0, 0, db2amp(-80), 0, db2amp(-60), 1) elif mode == 'speech-transcription': freqcurve = bpf.linear(0, 0, 50, 0, 80, 1, 200, 1, 250, 2, 4500, 2, 5000, 0) durcurve = bpf.linear(0, 0, 0.01, 0, 0.02, 1) ampcurve = bpf.linear(0, 0, db2amp(-75), 0, db2amp(-65), 1) else: raise KeyError("mode unknown, must be one of 'speech', 'transcription'") return PartialWeighter(freqcurve=freqcurve, durcurve=durcurve, ampcurve=ampcurve, freqweight=1, durweight=1, ampweight=2)