spessasynth_lib 3.14.1 → 3.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,6 +57,16 @@ export class SpessaSynthProcessor extends AudioWorkletProcessor
57
57
 
58
58
  this.transposition = 0;
59
59
 
60
+ /**
61
+ * this.tunings[program][key] = tuning
62
+ * @type {MTSProgramTuning[]}
63
+ */
64
+ this.tunings = [];
65
+ for (let i = 0; i < 127; i++)
66
+ {
67
+ this.tunings.push([]);
68
+ }
69
+
60
70
  /**
61
71
  * Bank offset for things like embedded RMIDIS. Added for every program change
62
72
  * @type {number}
@@ -31,17 +31,22 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
31
31
  return;
32
32
  }
33
33
 
34
- midiNote += channelObject.channelTransposeKeyShift;
34
+ let sentMidiNote = midiNote + channelObject.channelTransposeKeyShift;
35
35
 
36
36
  if(midiNote > 127 || midiNote < 0)
37
37
  {
38
38
  return;
39
39
  }
40
+ const program = channelObject.preset.program;
41
+ if(this.tunings[program][midiNote]?.midiNote >= 0)
42
+ {
43
+ sentMidiNote = this.tunings[program][midiNote].midiNote;
44
+ }
40
45
 
41
46
  // get voices
42
47
  const voices = getWorkletVoices(
43
48
  channel,
44
- midiNote,
49
+ sentMidiNote,
45
50
  velocity,
46
51
  channelObject.preset,
47
52
  startTime,
@@ -89,7 +94,7 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
89
94
  {
90
95
  this.sendChannelProperties();
91
96
  this.callEvent("noteon", {
92
- midiNote: midiNote - channelObject.channelTransposeKeyShift,
97
+ midiNote: midiNote,
93
98
  channel: channel,
94
99
  velocity: velocity,
95
100
  });
@@ -100,6 +100,13 @@ export function resetAllControllers()
100
100
  })
101
101
  }
102
102
  }
103
+ this.tunings = [];
104
+ this.tunings = [];
105
+ for (let i = 0; i < 127; i++)
106
+ {
107
+ this.tunings.push([]);
108
+ }
109
+
103
110
  this.setMIDIVolume(1);
104
111
  this.system = DEFAULT_SYNTH_MODE;
105
112
  }
@@ -3,34 +3,37 @@ import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
3
3
  import { midiControllers } from '../../../midi_parser/midi_message.js'
4
4
  import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from '../message_protocol/worklet_message.js'
5
5
 
6
+ /**
7
+ * KeyNum: tuning
8
+ * @typedef {MTSNoteTuning[]} MTSProgramTuning
9
+ */
10
+
11
+ /**
12
+ * @typedef {Object} MTSNoteTuning
13
+ * @property {number} midiNote - the base midi note to use, -1 means no change
14
+ * @property {number} centTuning - additional tuning
15
+ */
6
16
 
7
17
  /**
8
18
  * Calculates freqency for MIDI Tuning Standard
9
19
  * @param byte1 {number}
10
20
  * @param byte2 {number}
11
21
  * @param byte3 {number}
12
- * @return {number|null}
22
+ * @return {{midiNote: number, centTuning: number|null}}
13
23
  */
14
- function calculateFrequency(byte1, byte2, byte3)
24
+ function getTuning(byte1, byte2, byte3)
15
25
  {
16
- // handle special case of "no change"
26
+ const midiNote = byte1;
27
+ const fraction = (byte2 << 7) | byte3; // Combine byte2 and byte3 into a 14-bit number
28
+
29
+ // no change
17
30
  if (byte1 === 0x7F && byte2 === 0x7F && byte3 === 0x7F)
18
31
  {
19
- return null;
32
+ return { midiNote: -1, centTuning: null };
20
33
  }
21
34
 
22
- // Frequency base for MIDI note number 0
23
- const baseFrequency = 8.1758;
24
-
25
- const semitone = byte1;
26
-
27
- // combine byte2 and byte3
28
- const fraction = (byte2 << 7) | byte3;
29
-
30
- // get total cents
31
- const totalCents = semitone * 100 + (fraction / 16384) * 100;
32
-
33
- return baseFrequency * Math.pow(2, totalCents / 1200);
35
+ // calculate cent tuning
36
+ return { midiNote: midiNote, centTuning: fraction * 0.0061 };
34
37
  }
35
38
 
36
39
 
@@ -144,26 +147,32 @@ export function systemExclusive(messageData, channelOffset = 0)
144
147
  switch(messageData[3])
145
148
  {
146
149
  // single note change
150
+ // single note change bank
147
151
  case 0x02:
148
- const tuningProgram = messageData[4];
149
- const numberOfChanges = messageData[5];
150
- const keys = [];
151
- let currentMessageIndex = 6
152
- for (let i = 0; i < numberOfChanges; i++)
152
+ case 0x07:
153
+ let currentMessageIndex = 4;
154
+ if(messageData[3] === 0x07)
153
155
  {
154
- keys.push(messageData[currentMessageIndex]);
156
+ // skip bank
155
157
  currentMessageIndex++;
156
158
  }
157
- const frequencies = [];
159
+ // get program and number of cahnges
160
+ const tuningProgram = messageData[currentMessageIndex++];
161
+ const numberOfChanges = messageData[currentMessageIndex++];
158
162
  for (let i = 0; i < numberOfChanges; i++)
159
163
  {
160
- frequencies.push(calculateFrequency(
164
+ // set the given tuning to the program
165
+ this.tunings[tuningProgram][messageData[currentMessageIndex++]] = getTuning(
161
166
  messageData[currentMessageIndex++],
162
167
  messageData[currentMessageIndex++],
163
168
  messageData[currentMessageIndex++],
164
- ));
169
+ );
165
170
  }
166
- console.log(tuningProgram, numberOfChanges, keys, frequencies)
171
+ SpessaSynthInfo(`%cSingle Note Tuning. Program: %c${tuningProgram}%c Keys affected: ${numberOfChanges}`,
172
+ consoleColors.info,
173
+ consoleColors.recognized,
174
+ consoleColors.info,
175
+ consoleColors.recognized)
167
176
  break;
168
177
 
169
178
  // octave tuning (1 byte)
@@ -250,7 +259,6 @@ export function systemExclusive(messageData, channelOffset = 0)
250
259
  // http://www.bandtrax.com.au/sysex.htm
251
260
  // https://cdn.roland.com/assets/media/pdf/AT-20R_30R_MI.pdf
252
261
  case 0x41:
253
- // messagedata[1] is device id (ignore as we're everything >:) )
254
262
  if(messageData[2] === 0x42 && messageData[3] === 0x12)
255
263
  {
256
264
  // this is a GS sysex
@@ -55,6 +55,7 @@ export function renderVoice(
55
55
  }
56
56
 
57
57
  // TUNING
58
+ let targetKey = voice.targetKey;
58
59
 
59
60
  // calculate tuning
60
61
  let cents = voice.modulatedGenerators[generatorTypes.fineTune]
@@ -65,8 +66,16 @@ export function renderVoice(
65
66
  let semitones = voice.modulatedGenerators[generatorTypes.coarseTune]
66
67
  + channel.customControllers[customControllers.channelTuningSemitones];
67
68
 
69
+ // midi tuning standard
70
+ const tuning = this.tunings[channel.preset.program][targetKey];
71
+ if(tuning?.midiNote >= 0)
72
+ {
73
+ targetKey = tuning.midiNote;
74
+ cents += tuning.centTuning;
75
+ }
76
+
68
77
  // calculate tuning by key
69
- cents += (voice.targetKey - voice.sample.rootKey) * voice.modulatedGenerators[generatorTypes.scaleTuning];
78
+ cents += (targetKey - voice.sample.rootKey) * voice.modulatedGenerators[generatorTypes.scaleTuning];
70
79
 
71
80
  // vibrato LFO
72
81
  const vibratoDepth = voice.modulatedGenerators[generatorTypes.vibLfoToPitch];