|
| 1 | +import * as Tone from 'tone'; |
| 2 | + |
| 3 | +class ComplementaryToneGenerator { |
| 4 | + constructor() { |
| 5 | + this.rootNote = 'C'; |
| 6 | + this.currentOctave = 4; // Starting octave |
| 7 | + this.octaveDirection = 1; // 1 for up, -1 for down |
| 8 | + this.octaveMin = 3; // Minimum octave limit |
| 9 | + this.octaveMax = 5; // Maximum octave limit |
| 10 | + this.scalePatterns = { |
| 11 | + major: [2, 2, 1, 2, 2, 2, 1], // W-W-H-W-W-W-H |
| 12 | + minor: [2, 1, 2, 2, 1, 2, 2], // W-H-W-W-H-W-W |
| 13 | + majorSeventh: [4, 3, 1], // Major 7th Chord (Root-Major 3rd-Perfect 5th-Major 7th) |
| 14 | + pentatonic: [2, 2, 3, 2, 3], // Pentatonic Scale |
| 15 | + }; |
| 16 | + this.scaleKeys = Object.keys(this.scalePatterns); |
| 17 | + this.currentScalePatternIndex = 0; // Index to keep track of the current scale pattern |
| 18 | + this.currentScalePattern = this.scaleKeys[this.currentScalePatternIndex]; |
| 19 | + this.currentScale = []; |
| 20 | + this.scaleIndex = 0; |
| 21 | + this.lastFrequency = null; |
| 22 | + |
| 23 | + this.synth = new Tone.Synth({ |
| 24 | + envelope: { |
| 25 | + attack: 0.005, |
| 26 | + decay: 0.1, |
| 27 | + sustain: 0.1, |
| 28 | + release: 0.5, |
| 29 | + }, |
| 30 | + }).toDestination(); |
| 31 | + |
| 32 | + this.initializeScale(); |
| 33 | + } |
| 34 | + |
| 35 | + generateScale(rootNote, pattern) { |
| 36 | + let scale = [`${rootNote}${this.currentOctave}`]; |
| 37 | + let currentFrequency = Tone.Frequency(scale[0]).toFrequency(); |
| 38 | + |
| 39 | + pattern.forEach(interval => { |
| 40 | + currentFrequency *= Tone.intervalToFrequencyRatio(interval); |
| 41 | + scale.push(Tone.Frequency(currentFrequency).toNote()); |
| 42 | + }); |
| 43 | + |
| 44 | + return scale; |
| 45 | + } |
| 46 | + |
| 47 | + initializeScale() { |
| 48 | + this.currentScale = this.generateScale(this.rootNote, this.scalePatterns[this.currentScalePattern]); |
| 49 | + this.scaleIndex = 0; // Reset scale index |
| 50 | + } |
| 51 | + |
| 52 | + playNextNote() { |
| 53 | + // Check if we've reached the end or start of the scale to change direction |
| 54 | + if (this.scaleIndex >= this.currentScale.length - 1 || this.scaleIndex <= 0) { |
| 55 | + this.adjustOctaveAndMaybeScale(); |
| 56 | + } |
| 57 | + |
| 58 | + const nextNote = this.currentScale[this.scaleIndex]; |
| 59 | + this.synth.triggerAttackRelease(nextNote, '8n', Tone.now()); |
| 60 | + this.scaleIndex += this.octaveDirection; // Move index in the current direction |
| 61 | + this.lastFrequency = Tone.Frequency(nextNote).toFrequency(); |
| 62 | + } |
| 63 | + |
| 64 | + adjustOctaveAndMaybeScale() { |
| 65 | + // When changing direction at octave limits, also change the scale |
| 66 | + if (this.currentOctave >= this.octaveMax || this.currentOctave <= this.octaveMin) { |
| 67 | + this.octaveDirection *= -1; // Change direction |
| 68 | + this.selectNextScaleAndRoot(); // Change scale and root note |
| 69 | + } else if (this.scaleIndex === 0 || this.scaleIndex === this.currentScale.length - 1) { |
| 70 | + // Regular octave direction change without hitting octave limits |
| 71 | + this.octaveDirection *= -1; // Change direction |
| 72 | + } |
| 73 | + |
| 74 | + // Adjust the octave within the limits |
| 75 | + this.currentOctave += this.octaveDirection; |
| 76 | + if (this.currentOctave > this.octaveMax) { |
| 77 | + this.currentOctave = this.octaveMax; |
| 78 | + } else if (this.currentOctave < this.octaveMin) { |
| 79 | + this.currentOctave = this.octaveMin; |
| 80 | + } |
| 81 | + |
| 82 | + this.initializeScale(); // Re-initialize the scale with the new settings |
| 83 | + } |
| 84 | + |
| 85 | + selectNextScaleAndRoot() { |
| 86 | + // Move to the next scale pattern |
| 87 | + this.currentScalePatternIndex = (this.currentScalePatternIndex + 1) % this.scaleKeys.length; |
| 88 | + this.currentScalePattern = this.scaleKeys[this.currentScalePatternIndex]; |
| 89 | + |
| 90 | + const rootNotes = ['C', 'D', 'E', 'B', 'G', 'F', 'A']; |
| 91 | + let currentRootIndex = rootNotes.indexOf(this.rootNote); |
| 92 | + currentRootIndex = (currentRootIndex + 1) % rootNotes.length; |
| 93 | + this.rootNote = rootNotes[currentRootIndex]; |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +export default ComplementaryToneGenerator; |
0 commit comments