UNPKG

5.18 kBJavaScriptView Raw
1import { FeedbackEffect } from "./FeedbackEffect.js";
2import { optionsFromArguments } from "../core/util/Defaults.js";
3import { LFO } from "../source/oscillator/LFO.js";
4import { Delay } from "../core/context/Delay.js";
5import { CrossFade } from "../component/channel/CrossFade.js";
6import { Signal } from "../signal/Signal.js";
7import { readOnly } from "../core/util/Interface.js";
8import { intervalToFrequencyRatio } from "../core/type/Conversions.js";
9/**
10 * PitchShift does near-realtime pitch shifting to the incoming signal.
11 * The effect is achieved by speeding up or slowing down the delayTime
12 * of a DelayNode using a sawtooth wave.
13 * Algorithm found in [this pdf](http://dsp-book.narod.ru/soundproc.pdf).
14 * Additional reference by [Miller Pucket](http://msp.ucsd.edu/techniques/v0.11/book-html/node115.html).
15 * @category Effect
16 */
17export class PitchShift extends FeedbackEffect {
18 constructor() {
19 const options = optionsFromArguments(PitchShift.getDefaults(), arguments, ["pitch"]);
20 super(options);
21 this.name = "PitchShift";
22 this._frequency = new Signal({ context: this.context });
23 this._delayA = new Delay({
24 maxDelay: 1,
25 context: this.context,
26 });
27 this._lfoA = new LFO({
28 context: this.context,
29 min: 0,
30 max: 0.1,
31 type: "sawtooth",
32 }).connect(this._delayA.delayTime);
33 this._delayB = new Delay({
34 maxDelay: 1,
35 context: this.context,
36 });
37 this._lfoB = new LFO({
38 context: this.context,
39 min: 0,
40 max: 0.1,
41 type: "sawtooth",
42 phase: 180,
43 }).connect(this._delayB.delayTime);
44 this._crossFade = new CrossFade({ context: this.context });
45 this._crossFadeLFO = new LFO({
46 context: this.context,
47 min: 0,
48 max: 1,
49 type: "triangle",
50 phase: 90,
51 }).connect(this._crossFade.fade);
52 this._feedbackDelay = new Delay({
53 delayTime: options.delayTime,
54 context: this.context,
55 });
56 this.delayTime = this._feedbackDelay.delayTime;
57 readOnly(this, "delayTime");
58 this._pitch = options.pitch;
59 this._windowSize = options.windowSize;
60 // connect the two delay lines up
61 this._delayA.connect(this._crossFade.a);
62 this._delayB.connect(this._crossFade.b);
63 // connect the frequency
64 this._frequency.fan(this._lfoA.frequency, this._lfoB.frequency, this._crossFadeLFO.frequency);
65 // route the input
66 this.effectSend.fan(this._delayA, this._delayB);
67 this._crossFade.chain(this._feedbackDelay, this.effectReturn);
68 // start the LFOs at the same time
69 const now = this.now();
70 this._lfoA.start(now);
71 this._lfoB.start(now);
72 this._crossFadeLFO.start(now);
73 // set the initial value
74 this.windowSize = this._windowSize;
75 }
76 static getDefaults() {
77 return Object.assign(FeedbackEffect.getDefaults(), {
78 pitch: 0,
79 windowSize: 0.1,
80 delayTime: 0,
81 feedback: 0,
82 });
83 }
84 /**
85 * Repitch the incoming signal by some interval (measured in semi-tones).
86 * @example
87 * const pitchShift = new Tone.PitchShift().toDestination();
88 * const osc = new Tone.Oscillator().connect(pitchShift).start().toDestination();
89 * pitchShift.pitch = -12; // down one octave
90 * pitchShift.pitch = 7; // up a fifth
91 */
92 get pitch() {
93 return this._pitch;
94 }
95 set pitch(interval) {
96 this._pitch = interval;
97 let factor = 0;
98 if (interval < 0) {
99 this._lfoA.min = 0;
100 this._lfoA.max = this._windowSize;
101 this._lfoB.min = 0;
102 this._lfoB.max = this._windowSize;
103 factor = intervalToFrequencyRatio(interval - 1) + 1;
104 }
105 else {
106 this._lfoA.min = this._windowSize;
107 this._lfoA.max = 0;
108 this._lfoB.min = this._windowSize;
109 this._lfoB.max = 0;
110 factor = intervalToFrequencyRatio(interval) - 1;
111 }
112 this._frequency.value = factor * (1.2 / this._windowSize);
113 }
114 /**
115 * The window size corresponds roughly to the sample length in a looping sampler.
116 * Smaller values are desirable for a less noticeable delay time of the pitch shifted
117 * signal, but larger values will result in smoother pitch shifting for larger intervals.
118 * A nominal range of 0.03 to 0.1 is recommended.
119 */
120 get windowSize() {
121 return this._windowSize;
122 }
123 set windowSize(size) {
124 this._windowSize = this.toSeconds(size);
125 this.pitch = this._pitch;
126 }
127 dispose() {
128 super.dispose();
129 this._frequency.dispose();
130 this._delayA.dispose();
131 this._delayB.dispose();
132 this._lfoA.dispose();
133 this._lfoB.dispose();
134 this._crossFade.dispose();
135 this._crossFadeLFO.dispose();
136 this._feedbackDelay.dispose();
137 return this;
138 }
139}
140//# sourceMappingURL=PitchShift.js.map
\No newline at end of file