def difftone_sources_from_set(difftone: pitch_t, sources: List[T], tomidi: Callable[[T], float] = asmidi, maxdeviation=0.5, exclude_intervals=None, exclude_intervals_maxdeviation=0.5, gap=0) -> List[Tuple[T, T]]: difftone_pitch = asmidi(difftone) f0 = m2f(difftone_pitch - maxdeviation) f1 = m2f(difftone_pitch + maxdeviation) possible_sources = [ source for source in sources if tomidi(source) >= difftone_pitch + gap ] results = [] for s1, s2 in combinations(possible_sources, 2): p1 = tomidi(s1) p2 = tomidi(s2) if p1 > p2: p1, p2 = p2, p1 s1, s2, = s2, s1 ok = True if exclude_intervals: interval_now = abs(p1 - p2) for excluded in exclude_intervals: if abs(interval_now - excluded) < exclude_intervals_maxdeviation: ok = False break if not ok: continue diff_freq = abs(m2f(p1) - m2f(p2)) if f0 <= diff_freq <= f1: results.append((s1, s2)) results.sort(key=lambda pair: tomidi(pair[0])) return results
def ringmod_sources2(sideband1: pitch_t, sideband2: pitch_t, minnote: pitch_t = "A0", maxnote: pitch_t = "C8", maxdiff=0.5) -> List[Tuple[Note, Note]]: """ Find all pairs of two frequencies which produce the given sidebands when ringmodulated Returns a list of Notes minnote, maxnote: the range for possible answers maxdiff: the max. difference between the given sidebands and the resulting sidebands, in midi (1=semitone) """ difm = asmidi(sideband1) summ = asmidi(sideband2) midimin = n2m(minnote) if isinstance(minnote, str) else minnote midimax = n2m(maxnote) if isinstance(maxnote, str) else maxnote results = [] for midi1, midi2 in combinations(range(int(midimin), int(ceil(midimax))), 2): note0, note1 = ringmod_exactsource(m2f(midi1), m2f(midi2)) f0 = note0.freq f1 = note1.freq if abs(f2m(f0 + f1) - summ) <= maxdiff and abs(f2m(abs(f1 - f0)) - difm) <= maxdiff: results.append((note0, note1)) results.sort() return results
def ringmod_exactsource(sideband1: pitch_t, sideband2: pitch_t) -> Tuple[Note, Note]: """ Find a pair of frequencies which, when ringmodulated, result in the given sidebands sideband1, sideband2: the sidebands produced, as notename or midinote Returns: the original pitches, as midinotes """ diffFreq = m2f(asmidi(sideband1)) sumFreq = m2f(asmidi(sideband2)) f1 = (diffFreq + sumFreq) / 2.0 f0 = sumFreq - f1 return Note(f2m(f0)), Note(f2m(f1))
def sumtones_sources(result, maxdist=0.5, maxnote="C8", intervals=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], show=False, minnote="A0", difftonegap=0): """ find two notes which produce a sumation tone near the given note Args: result: the resulting difference tone, as note maxdist: the maximum distance between the expected result and the generated tone, in semitones (0.5 == 50 cents) maxnote: the highest note to considere intervals: accepted intervals between the notes. Only 'tempered' notes are used, but of course microtones produce also difference tones! difftonegap : minimum gap (absolute value) between the sumtone and the difftone produced by the two notes show: either False or one of: 'report': produce a text report 'musicxml': convert to musicxml, show as Finale file 'lily': convert to lilypond, show as PDF """ result = asmidi(result) maxnote = asmidi(maxnote) pairs = _sumtone_find_source(result, maxdist, minnote=minnote, maxnote=maxnote, intervals=intervals, difftonegap=difftonegap) if not pairs: if show == 'report': warnings.warn( "No source pair of notes found that satisfy the given conditions" ) return [] out = [] resultfreq = m2f(result) for note0, note1 in pairs: sumfreq = n2f(note0) + n2f(note1) sumtone = SumTone((note0, note1), f2n(sumfreq), sumfreq, abs(sumfreq - resultfreq)) out.append(sumtone) if show == 'report': raise NotImplementedError() # _sumtones_sources_report(result, out) elif show: from maelzel import scoring events = [] for pair in out: notes = [] notes.extend(pair.notes) notes.append(pair.sumnote) chord = scoring.Notation(pitches=notes) chord.addArticulation('sum: %.2f' % pair.sumfreq) events.append(chord) scoring.render.show(events) return RecordList(out)
def pushState(a4: float = None, tempo: time_t = None, renderer=None, config: configdict.CheckedDict = None, scorestruct: ScoreStructure = None) -> _State: """ Push a new state to the global state stack. A new state inherits values not set from the earlier state Args: a4: the reference frequency tempo: a tempo reference renderer: a play.OfflineRenderer config: a configuration dict, as created by config.clone() scorestruct: the current ScoreStructure. """ assert len(_statestack) >= 1 assert tempo is None or scorestruct is None currState = _statestack[-1] if a4 is None: a4 = currState.a4 tempo = F(tempo) if tempo is not None else currState.tempo if renderer is None: renderer = currState.renderer if config is None: config = currState.config.copy() else: assert config is not currentConfig() and \ not isinstance(config, configdict.ConfigDict) state = _State(a4=a4, tempo=tempo, renderer=renderer, config=config) _statestack.append(state) if a4 != m2f(69): set_reference_freq(a4) return state
def sound2harmonic(self, note, kind='all', tolerance=0.5): """ find the harmonics in this string which can produce the given sound as result. note: the note to produce (a string note) kind: kind of harmonic. One of [4, 3M, 3m, natural, all] tolerance: the acceptable difference between the desired note and the result (in semitones) """ midinote = n2m(note) if kind == '4' or kind == 4: f0 = midinote - 24 out = self.sound2note(m2n(f0)) elif kind == '3M' or kind == 3: f0 = midinote - 28 out = self.sound2note(m2n(f0)) elif kind == '3m': f0 = midinote - 31 out = self.sound2note(m2n(f0)) elif kind in ('n', 'natural'): fundamental = m2f(self._sounding_midi) harmonics = [f2m(fundamental * harmonic) for harmonic in range(12)] acceptable_harmonics = [] for harmonic in harmonics: if abs(harmonic - midinote) <= tolerance: acceptable_harmonics.append(harmonic) if len(acceptable_harmonics) > 0: # now find the position of the node in the string results = [] nodes = [] for harmonic in acceptable_harmonics: fret = self._flageolet_string.ratio2fret( m2f(harmonic) / fundamental) nodes.append(fret) for node in nodes: for fret_pos in node.frets_pos: results.append(fret_pos[1]) # we only append the pitch out = [self.sound2note(result) for result in results] else: out = None elif kind == 'all': out = [] for kind in ('4 3M 3m n'.split()): out.append( self.sound2harmonic(note, kind=kind, tolerance=tolerance)) return out return kind, out
def _sumtone_find_source(pitch, maxdist=0.5, intervals: List[U[int, float]] = None, minnote: pitch_t = 'A0', maxnote: pitch_t = 'C8', difftonegap=0.): """ pitch: a note str or a midinote maxdist: the max. distance between the given note and # the produced sumtone intervals: allowed intervals for the source pitches minnote, maxnote: the range to look for sumtones Returns: a list of pairs (note1:str, note2:str) representing notes which produce the given pitch as a sumtone (or an empty list if no pairs are found) """ intervals = intervals or [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] m0 = asmidi(pitch) midimin = int(n2m(minnote)) if isinstance(minnote, str) else int(minnote) midimax = int(n2m(maxnote)) if isinstance(maxnote, str) else int(maxnote) minValidFreq = 1 results = [] for interval in intervals: for midi1 in range(midimin, midimax): midi2 = midi1 + interval if midi2 > midimax: continue sumFreq = m2f(midi1) + m2f(midi2) if sumFreq <= minValidFreq: continue diffFreq = abs(m2f(midi1) - m2f(midi2)) if diffFreq < 20 or (abs(f2m(diffFreq) - f2m(sumFreq)) < difftonegap): continue midiError = abs(f2m(sumFreq) - m0) if midiError <= maxdist: results.append((midiError, (midi1, midi2))) if not results: return [] results.sort() # secondary sort by minimal difference pairs = list(set(midipair for diff, midipair in results)) pairs.sort() # primary sort by pitch out = [(m2n(m1), m2n(m2)) for m1, m2 in pairs] assert all(isinstance(n0, str) and isinstance(n1, str) for n0, n1 in out) return out
def difftone_evaluate_inharmonicity(pitch1: pitch_t, pitch2: pitch_t) -> float: m1 = asmidi(pitch1) m2 = asmidi(pitch2) if m1 > m2: m1, m2 = m2, m1 md = f2m(abs(m2f(m1) - m2f(m2))) f1, f2, fd = map(m2f, (m1, m2, md)) rat_2_1 = f2 / f1 rat_2_d = f2 / fd rat_1_d = f1 / fd ratq_2_1 = misc.snap_to_grid(rat_2_1, 0.5) ratq_2_d = misc.snap_to_grid(rat_2_d, 0.5) ratq_1_d = misc.snap_to_grid(rat_1_d, 0.5) delta_2_1 = abs(rat_2_1 - ratq_2_1) / rat_2_1 delta_2_d = abs(rat_2_d - ratq_2_d) / rat_2_d delta_1_d = abs(rat_1_d - ratq_1_d) / rat_1_d print(delta_2_1, delta_2_d, delta_1_d) delta = sqrt(delta_2_1**2 + delta_2_d**2 + delta_1_d**2) return delta
def difftones(*pitches: pitch_t) -> List[Note]: """ Calculate the difference tones generated by all the pair combinations of notes SEE ALSO: difftones_sources, difftone_sources_in_range """ midis = _parsePitches(*pitches) freqs = [m2f(m) for m in midis] return [Note(f2m(abs(f2 - f1))) for f1, f2 in combinations(freqs, 2)]
def _difftone_find_source(pitch, maxdeviation=0.5, intervals=None, minnote: pitch_t = 'A0', maxnote: pitch_t = 'C8', resolution=1.) -> List[Tuple[float, float]]: """ Args: pitch: the resulting difftone maxdeviation: the max. deviation between the generated difftone and the desired one intervals: a list of possible intervals (None to allow any interval) minnote: the lowest note to search maxnote: the highest note to search resolution: the pitch resolution of the search space (1=semitone) Returns: a list of (lowest note, highest note)) """ pitch = asmidi(pitch) pitchfreq = m2f(pitch) intervals = intervals or list(frange(resolution, 8, resolution)) f0 = m2f(pitch - maxdeviation) f1 = m2f(pitch + maxdeviation) minmidi = asmidi(minnote) maxmidi = asmidi(maxnote) out = [] for interval in intervals: for m0 in frange(minmidi, maxmidi + resolution, resolution): m1 = m0 + interval if m1 >= maxmidi + resolution: continue diffFreq = abs(m2f(m0) - m2f(m1)) if diffFreq > f1: break elif f0 <= diffFreq <= f1: out.append((abs(diffFreq - pitchfreq), (m0, m1))) if out: out.sort() out = [pair for error, pair in out] out.sort() return out
def difftones_beatings( pitch1: pitch_t, pitch2: pitch_t, maxbeatings=20, minbeatings=0.3, wave="saw", ) -> List[DifftoneBeating]: """ estimate the beatings generated by the difference tone(s) between note1 and note2 for each pair of notes other than sinus-tones, many difference tones are generated: * between the fundamentals * between the overtones The calculation is based on the relative amplitude of the overtones and presuposes simple waveforms. Any other routine would have to work with the actual sample data to calculate the beatings. Using saw waves gives a sort of upper limit to the beatings which can be generated between two notes. In reality, the amplitude of the beatings will always be less than what is reported here. """ defaultwave = wave freq1 = m2f(asmidi(pitch1)) freq2 = m2f(asmidi(pitch2)) overtones1 = [freq1 * i for i in range(1, 7)] overtones2 = [freq2 * i for i in range(1, 7)] pairs = [] for i, o1 in enumerate(overtones1): for j, o2 in enumerate(overtones2): if minbeatings <= abs(o1 - o2) <= maxbeatings: amp1 = wave_overtone_relative_amplitude(defaultwave, i) amp2 = wave_overtone_relative_amplitude(defaultwave, j) amp = amp1 * amp2 if amp > 0: pairs.append((amp, o1, o2)) pairs.sort(reverse=True) pairs2 = [ DifftoneBeating(abs(o1 - o2), Note(f2m(o1)), Note(f2m(o2)), freq1, freq2, amp) for amp, o1, o2 in pairs ] return pairs2
def difftones_cubic(*notes: pitch_t) -> List[Note]: """ Return the cubic difference tones if f1 and f2 are pure tones and f1 < f2, the cubic diff-tone is 2*f1 - f2 """ freqs = [m2f(asmidi(note)) for note in notes] out = [] for f1, f2 in combinations(freqs, 2): if f2 < f1: f1, f2 = f2, f1 f0 = f1 * 2 - f2 out.append(Note(f2m(f0))) return out
def difftone_source(difftone: pitch_t, interval: float, resolution=0.0) -> Difftone: """ difftone: the resulting difftone interval: the interval between the two notes which should produce a difference tone close to `difftone` resolution: the resolution of the pitch grid for the source notes. This also defines the acceptable error of the resulting difftone Returns: a Difftone """ diffFreq = m2f(asmidi(difftone)) ratio = interval2ratio(interval) f0 = diffFreq / (ratio - 1) f1 = f0 * ratio midi0, midi1 = f2m(f0), f2m(f1) if resolution > 0: midi0 = misc.snap_to_grid(midi0, resolution) midi1 = misc.snap_to_grid(midi1, resolution) return Difftone(midi0, midi1, difftone)
if self is currentConfig(): set_reference_freq(value) @property def tempo(self) -> float: return float(self.scorestruct.getMeasureDef(0).quarterTempo) @tempo.setter def tempo(self, quarterTempo: float): pass def activate(self) -> None: set_reference_freq(self.a4) _statestack = [_State(a4=config.get('A4', m2f(69)), config=config)] def pushState(a4: float = None, tempo: time_t = None, renderer=None, config: configdict.CheckedDict = None, scorestruct: ScoreStructure = None) -> _State: """ Push a new state to the global state stack. A new state inherits values not set from the earlier state Args: a4: the reference frequency tempo: a tempo reference renderer: a play.OfflineRenderer
def gradient(t, f): semitones = curve(t) f2 = m2f(f2m(f) + semitones) return f2
def transpose(spectrum: sp.Spectrum, semitones:float) -> sp.Spectrum: """ Transpose spectrum by a fixed number of semitones """ curve = bpf.asbpf(lambda f: m2f((f2m(f)+semitones))) return spectrum.freqwarp(curve)
def piano_freqs(start='A0', stop='C8') -> numpy.ndarray: """ generate an array of the frequencies representing all the piano keys """ keys = range(int(n2m(start)), int(n2m(stop))+1) return numpy.fromiter((m2f(key) for key in keys), dtype=numpy.float64)
def logfreqs(delta_midi=0.5) -> numpy.ndarray: """ return an array of frequencies corresponding to the pitches of all notes of the audible spectrum delta_midi indicates the definition of the pitch matrix: 1=semitones, 0.5, quartertones, etc. """ return numpy.fromiter((m2f(x) for x in numpy.arange(0, 139, delta_midi)), dtype=numpy.float64)
def gradient(t, f): semitones = curve(t) f2 = m2f(f2m(f) + semitones) return f2
def transpose(spectrum: sp.Spectrum, semitones: float) -> sp.Spectrum: """ Transpose spectrum by a fixed number of semitones """ curve = bpf.asbpf(lambda f: m2f((f2m(f) + semitones))) return spectrum.freqwarp(curve)
def ringmod_sources(sidebands: pitch_t, minnote: pitch_t = "A0", maxnote: pitch_t = "C8", maxdiff=0.5, matchall=True, constraints=None, numsources=None) -> List[Note]: """ Given a seq. of sidebands, find notes that, when ringmodulated together, include these sidebands as the result. sidebands: a seq. of sidebands, as notenames or frequencies minnote, maxnote: limit the possible range of the sourcefreqs matchall: if True, all sidebands should be matched constraints: if given, a seq. of functions. Each of these constraints is of the form (midisources, sidebands) -> bool where: midisources: the sources being modulated (as midinotes) sidebands: the sidebands generated (as midinote) Example: find a seq. of frequencies which generate the folowing sidebands, given that all sidebands should lie within the interval C2-C6 sidebands = ["C4", "E4", "B5"] constraints = [lambda freqs: all(n2f("C2") <= freq <= n2f("C6") for freq in freqs)] sourcefreqs = ringmod_sources(sidebands, matchall=True, constraints=constraints) print(map(f2n, sourcefreqs)) --> ['2C#', '4E', '5E'] newsidebands = ringmod(*sourcefreqs) print(map(f2n, newsidebands)) --> ['4C-09', '4E', '4G+30', '5D+08', '5Gb-27', '5B+02'] """ assert isinstance(sidebands, (list, tuple)) sidemidis = [asmidi(sb) for sb in sidebands] midi0, midi1 = n2m(minnote), n2m(maxnote) if len(sidebands) == 2 and (numsources == 2 or numsources is None): note1, note2 = ringmod_exactsource(sidebands[0], sidebands[1]) return [note1, note2] elif len(sidebands) > 6 or numsources is not None and numsources > 3: raise NotImplementedError("too many sidebands...") bestmatch = [] sourcefreqs = None for m0, m1, m2 in combinations(range(int(midi0), int(ceil(midi1))), 3): newbands = ringmod(m0, m1, m2) newmidis = [band.midi for band in newbands] matching = _matchone(sidemidis, newmidis, maxdiff) if not matching: continue elif matchall and len(matching) < len(sidebands): continue elif constraints and not all( constr([m0, m1, m2], newmidis) for constr in constraints): continue if len(matching) > len(bestmatch): bestmatch = matching sourcefreqs = [m2f(m) for m in newmidis] elif len(matching) == len(bestmatch): newdiff = sum(abs(orig - new) for orig, new in matching) lastdiff = sum(abs(orig - last) for orig, last in bestmatch) if newdiff < lastdiff: sourcefreqs = [m2f(m) for m in newmidis] bestmatch = matching return [Note(f2m(freq)) for freq in sourcefreqs]