spessasynth_lib 3.20.27 → 3.20.29

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.
@@ -59,6 +59,7 @@ export namespace messageTypes {
59
59
  export let lyric: number;
60
60
  export let marker: number;
61
61
  export let cuePoint: number;
62
+ export let programName: number;
62
63
  export let midiChannelPrefix: number;
63
64
  export let midiPort: number;
64
65
  export let endOfTrack: number;
@@ -1,6 +1,6 @@
1
1
  import { dataBytesAmount, getChannel, messageTypes, MidiMessage } from './midi_message.js'
2
2
  import { IndexedByteArray } from '../utils/indexed_array.js'
3
- import { arrayToHexString, consoleColors, formatTitle } from '../utils/other.js'
3
+ import { consoleColors, formatTitle } from '../utils/other.js'
4
4
  import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from '../utils/loggin.js'
5
5
  import { readRIFFChunk } from '../soundfont/basic_soundfont/riff_chunk.js'
6
6
  import { readVariableLengthQuantity } from '../utils/byte_functions/variable_length_quantity.js'
@@ -10,6 +10,8 @@ import { readLittleEndian } from '../utils/byte_functions/little_endian.js'
10
10
  import { RMIDINFOChunks } from './rmidi_writer.js'
11
11
  import { BasicMIDI, MIDIticksToSeconds } from './basic_midi.js'
12
12
 
13
+
14
+ const GS_TEXT_HEADER = new Uint8Array([0x41, 0x10, 0x45, 0x12, 0x10, 0x00, 0x00]);
13
15
  /**
14
16
  * midi_loader.js
15
17
  * purpose: parses a midi file for the seqyencer, including things like marker or CC 2/4 loop detection, copyright detection etc.
@@ -366,7 +368,7 @@ class MIDI extends BasicMIDI
366
368
  // since this is a sysex message
367
369
  // check for embedded copyright (roland SC display sysex) http://www.bandtrax.com.au/sysex.htm
368
370
  // header goes like this: 41 10 45 12 10 00 00
369
- if(arrayToHexString(eventData.slice(0, 7)).trim() === "41 10 45 12 10 00 00")
371
+ if(eventData.slice(0, 7).every((n, i) => GS_TEXT_HEADER[i] === n))
370
372
  {
371
373
  /**
372
374
  * @type {IndexedByteArray}
@@ -110,6 +110,7 @@ export const messageTypes = {
110
110
  lyric: 0x05,
111
111
  marker: 0x06,
112
112
  cuePoint: 0x07,
113
+ programName: 0x08,
113
114
  midiChannelPrefix: 0x20,
114
115
  midiPort: 0x21,
115
116
  endOfTrack: 0x2F,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.20.27",
3
+ "version": "3.20.29",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -1,5 +1,7 @@
1
1
  import { returnMessageType } from '../../synthetizer/worklet_system/message_protocol/worklet_message.js'
2
2
  import { WorkletSequencerMessageType, WorkletSequencerReturnMessageType } from './sequencer_message.js'
3
+ import { messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
4
+ import { MIDI_CHANNEL_COUNT } from '../../synthetizer/synthetizer.js'
3
5
 
4
6
  /**
5
7
  * @param messageType {WorkletSequencerMessageType}
@@ -94,4 +96,69 @@ export function post(messageType, messageData = undefined)
94
96
  export function sendMIDIMessage(message)
95
97
  {
96
98
  this.post(WorkletSequencerReturnMessageType.midiEvent, message);
99
+ }
100
+
101
+ /**
102
+ * @this {WorkletSequencer}
103
+ * @param channel {number}
104
+ * @param type {number}
105
+ * @param value {number}
106
+ */
107
+ export function sendMIDICC(channel, type, value)
108
+ {
109
+ channel %= 16;
110
+ if(!this.sendMIDIMessages)
111
+ {
112
+ return;
113
+ }
114
+ this.sendMIDIMessage([messageTypes.controllerChange | channel, type, value])
115
+ }
116
+
117
+ /**
118
+ * @this {WorkletSequencer}
119
+ * @param channel {number}
120
+ * @param program {number}
121
+ */
122
+ export function sendMIDIProgramChange(channel, program)
123
+ {
124
+ channel %= 16;
125
+ if(!this.sendMIDIMessages)
126
+ {
127
+ return;
128
+ }
129
+ this.sendMIDIMessage([messageTypes.programChange | channel, program]);
130
+ }
131
+
132
+ /**
133
+ * Sets the pitch of the given channel
134
+ * @this {WorkletSequencer}
135
+ * @param channel {number} usually 0-15: the channel to change pitch
136
+ * @param MSB {number} SECOND byte of the MIDI pitchWheel message
137
+ * @param LSB {number} FIRST byte of the MIDI pitchWheel message
138
+ */
139
+ export function sendMIDIPitchWheel(channel, MSB, LSB)
140
+ {
141
+ channel %= 16;
142
+ if(!this.sendMIDIMessages)
143
+ {
144
+ return;
145
+ }
146
+ this.sendMIDIMessage([messageTypes.pitchBend | channel, LSB, MSB]);
147
+ }
148
+
149
+ /**
150
+ * @this {WorkletSequencer}
151
+ */
152
+ export function sendMIDIReset()
153
+ {
154
+ if(!this.sendMIDIMessages)
155
+ {
156
+ return;
157
+ }
158
+ this.sendMIDIMessage([messageTypes.reset]);
159
+ for(let ch = 0; ch < MIDI_CHANNEL_COUNT; ch++)
160
+ {
161
+ this.sendMIDIMessage([messageTypes.controllerChange | ch, midiControllers.allSoundOff, 0]);
162
+ this.sendMIDIMessage([messageTypes.controllerChange | ch, midiControllers.resetAllControllers, 0]);
163
+ }
97
164
  }
@@ -1,6 +1,5 @@
1
1
  import { getEvent, messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
2
2
  import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
3
- import { MIDI_CHANNEL_COUNT } from '../../synthetizer/synthetizer.js'
4
3
  import { MIDIticksToSeconds } from '../../midi_parser/basic_midi.js'
5
4
 
6
5
 
@@ -27,15 +26,8 @@ export function _playTo(time, ticks = undefined)
27
26
  this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision);
28
27
  // reset
29
28
  this.synth.resetAllControllers();
30
- if(this.sendMIDIMessages)
31
- {
32
- this.sendMIDIMessage([messageTypes.reset]);
33
- for(let ch = 0; ch < MIDI_CHANNEL_COUNT; ch++)
34
- {
35
- this.sendMIDIMessage([messageTypes.controllerChange | ch, midiControllers.resetAllControllers, 0]);
36
- }
37
- }
38
- this._resetTimers()
29
+ this.sendMIDIReset();
30
+ this._resetTimers();
39
31
 
40
32
  const channelsToSave = this.synth.workletProcessorChannels.length;
41
33
  /**
@@ -131,19 +123,19 @@ export function _playTo(time, ticks = undefined)
131
123
  const controllerNumber = event.messageData[0];
132
124
  if(isCCNonSkippable(controllerNumber))
133
125
  {
126
+ let ccV = event.messageData[1];
127
+ if(controllerNumber === midiControllers.bankSelect)
128
+ {
129
+ // add the bank to saved
130
+ programs[channel].bank = ccV;
131
+ break;
132
+ }
134
133
  if(this.sendMIDIMessages)
135
134
  {
136
- this.sendMIDIMessage([messageTypes.controllerChange | (channel % 16), controllerNumber, event.messageData[1]])
135
+ this.sendMIDICC(channel, controllerNumber, ccV);
137
136
  }
138
137
  else
139
138
  {
140
- let ccV = event.messageData[1];
141
- if(controllerNumber === midiControllers.bankSelect)
142
- {
143
- // add the bank to saved
144
- programs[channel].bank = ccV;
145
- break;
146
- }
147
139
  this.synth.controllerChange(channel, controllerNumber, ccV);
148
140
  }
149
141
  }
@@ -177,25 +169,29 @@ export function _playTo(time, ticks = undefined)
177
169
  // restoring saved controllers
178
170
  if(this.sendMIDIMessages)
179
171
  {
180
- // for all 16 channels
181
- for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++) {
182
- // send saved pitch bend
183
- this.sendMIDIMessage([messageTypes.pitchBend | (channelNumber % 16), pitchBends[channelNumber] & 0x7F, pitchBends[channelNumber] >> 7]);
184
-
185
- // every controller that has changed
186
- savedControllers[channelNumber].forEach((value, index) => {
187
- if(value !== defaultControllerArray[index] && !isCCNonSkippable(index))
188
- {
189
- this.sendMIDIMessage([messageTypes.controllerChange | (channelNumber % 16), index, value])
190
- }
191
- });
192
-
172
+ for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++)
173
+ {
174
+ // restore pitch bends
175
+ if(pitchBends[channelNumber] !== undefined)
176
+ {
177
+ this.sendMIDIPitchWheel(channelNumber, pitchBends[channelNumber] >> 7, pitchBends[channelNumber] & 0x7F);
178
+ }
179
+ if(savedControllers[channelNumber] !== undefined)
180
+ {
181
+ // every controller that has changed
182
+ savedControllers[channelNumber].forEach((value, index) => {
183
+ if(value !== defaultControllerArray[index] && !isCCNonSkippable(index))
184
+ {
185
+ this.sendMIDICC(channelNumber, index, value);
186
+ }
187
+ })
188
+ }
193
189
  // restore programs
194
190
  if(programs[channelNumber].program >= 0 && programs[channelNumber].actualBank >= 0)
195
191
  {
196
192
  const bank = programs[channelNumber].actualBank;
197
- this.sendMIDIMessage([messageTypes.controllerChange | (channelNumber % 16), midiControllers.bankSelect, bank]);
198
- this.sendMIDIMessage([messageTypes.programChange | (channelNumber % 16), programs[channelNumber].program]);
193
+ this.sendMIDICC(channelNumber, midiControllers.bankSelect, bank);
194
+ this.sendMIDIProgramChange(channelNumber, programs[channelNumber].program);
199
195
  }
200
196
  }
201
197
  }
@@ -101,6 +101,7 @@ export function _processEvent(event, trackIndex)
101
101
  case messageTypes.songPosition:
102
102
  case messageTypes.activeSensing:
103
103
  case messageTypes.keySignature:
104
+ case messageTypes.sequenceNumber:
104
105
  break;
105
106
 
106
107
  case messageTypes.text:
@@ -110,6 +111,7 @@ export function _processEvent(event, trackIndex)
110
111
  case messageTypes.marker:
111
112
  case messageTypes.cuePoint:
112
113
  case messageTypes.instrumentName:
114
+ case messageTypes.programName:
113
115
  this.post(WorkletSequencerReturnMessageType.textEvent, [event.messageData, statusByteData.status])
114
116
  break;
115
117
 
@@ -139,7 +141,8 @@ export function _processEvent(event, trackIndex)
139
141
  */
140
142
  export function _addNewMidiPort()
141
143
  {
142
- for (let i = 0; i < 16; i++) {
144
+ for (let i = 0; i < 16; i++)
145
+ {
143
146
  this.synth.createWorkletChannel(true);
144
147
  if(i === DEFAULT_PERCUSSION)
145
148
  {
@@ -4,7 +4,15 @@ import { _findFirstEventIndex, _processTick } from './process_tick.js'
4
4
  import { assignMIDIPort, loadNewSequence, loadNewSongList, nextSong, previousSong } from './song_control.js'
5
5
  import { _playTo, _recalculateStartTime, play, setTimeTicks } from './play.js'
6
6
  import { messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
7
- import { post, processMessage, sendMIDIMessage } from './events.js'
7
+ import {
8
+ post,
9
+ processMessage,
10
+ sendMIDICC,
11
+ sendMIDIMessage,
12
+ sendMIDIPitchWheel,
13
+ sendMIDIProgramChange,
14
+ sendMIDIReset,
15
+ } from './events.js'
8
16
  import { SpessaSynthWarn } from '../../utils/loggin.js'
9
17
  import { MIDI_CHANNEL_COUNT } from '../../synthetizer/synthetizer.js'
10
18
 
@@ -178,16 +186,21 @@ class WorkletSequencer
178
186
  {
179
187
  this.clearProcessHandler()
180
188
  // disable sustain
181
- for (let i = 0; i < 16; i++) {
189
+ for (let i = 0; i < 16; i++)
190
+ {
182
191
  this.synth.controllerChange(i, midiControllers.sustainPedal, 0);
183
192
  }
184
193
  this.synth.stopAllChannels();
185
194
  if(this.sendMIDIMessages)
186
195
  {
196
+ for(let note of this.playingNotes)
197
+ {
198
+ this.sendMIDIMessage([messageTypes.noteOff | (note.channel % 16), note.midiNote]);
199
+ }
187
200
  for (let c = 0; c < MIDI_CHANNEL_COUNT; c++)
188
201
  {
189
- this.sendMIDIMessage([messageTypes.controllerChange | c, 120, 0]); // all notes off
190
- this.sendMIDIMessage([messageTypes.controllerChange | c, 123, 0]); // all sound off
202
+ this.sendMIDICC(c, midiControllers.allNotesOff, 0);
203
+ this.sendMIDICC(c, midiControllers.allSoundOff, 0);
191
204
  }
192
205
  }
193
206
  }
@@ -218,9 +231,15 @@ class WorkletSequencer
218
231
  }
219
232
  }
220
233
 
221
- WorkletSequencer.prototype.post = post;
234
+ // Web MIDI sending
222
235
  WorkletSequencer.prototype.sendMIDIMessage = sendMIDIMessage;
236
+ WorkletSequencer.prototype.sendMIDIReset = sendMIDIReset;
237
+ WorkletSequencer.prototype.sendMIDICC = sendMIDICC;
238
+ WorkletSequencer.prototype.sendMIDIProgramChange = sendMIDIProgramChange;
239
+ WorkletSequencer.prototype.sendMIDIPitchWheel = sendMIDIPitchWheel;
223
240
  WorkletSequencer.prototype.assignMIDIPort = assignMIDIPort;
241
+
242
+ WorkletSequencer.prototype.post = post;
224
243
  WorkletSequencer.prototype.processMessage = processMessage;
225
244
 
226
245
  WorkletSequencer.prototype._processEvent = _processEvent;