File size: 3,424 Bytes
af6912c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//    abc_midi_create.js: Turn a linear series of events into a midi file.

var rendererFactory = require('../synth/abc_midi_renderer');

var create;

(function() {
	"use strict";

	var baseDuration = 480*4; // nice and divisible, equals 1 whole note

	create = function create(abcTune, options) {
		if (options === undefined) options = {};
		var commands = abcTune.setUpAudio(options);
		var midi = rendererFactory();
		var title = abcTune.metaText ? abcTune.metaText.title : undefined;
		if (title && title.length > 128) title = title.substring(0, 124) + '...';
		var key = abcTune.getKeySignature();
		var time = abcTune.getMeterFraction();

		// MAE 7 July 2024 - Fix for */8 meter tempos
		var tempo = commands.tempo;

		var beatsPerSecond = tempo / 60;

		// Fix tempo for */8 meters
		if (time.den == 8){

			// Compute the tempo based on the actual milliseconds per measure, scaled by the number of eight notes and halved to get tempo in bpm.
			var msPerMeasure = abcTune.millisecondsPerMeasure();
			
			tempo = (60000 / (msPerMeasure/time.num)) / 2;
			
			beatsPerSecond = tempo/60;

		}

		//var beatLength = abcTune.getBeatLength();
		midi.setGlobalInfo(tempo, title, key, time);
		
		for (var i = 0; i < commands.tracks.length; i++) {
			midi.startTrack();
			var notePlacement = {};
			for (var j = 0; j < commands.tracks[i].length; j++) {
				var event = commands.tracks[i][j];
				switch (event.cmd) {
					case 'text':
						midi.setText(event.type, event.text);
						break;
					case 'program':
						var pan = 0;
						if (options.pan && options.pan.length > i)
							pan = options.pan[i];
						if (event.instrument === 128) {
							// If we're using the percussion voice, change to Channel 10
							midi.setChannel(9, pan);
							midi.setInstrument(0);
						} else {
							midi.setChannel(event.channel, pan);
							midi.setInstrument(event.instrument);
						}
						break;
					case 'note':
						var gapLengthInBeats = event.gap * beatsPerSecond;
						var start = event.start;
						// The staccato and legato are indicated by event.gap.
						// event.gap is in seconds but the durations are in whole notes.
						var end = start + event.duration - gapLengthInBeats;
						if (!notePlacement[start])
							notePlacement[start] = [];
						notePlacement[start].push({ pitch: event.pitch, volume: event.volume, cents: event.cents });
						if (!notePlacement[end])
							notePlacement[end] = [];
						notePlacement[end].push({ pitch: event.pitch, volume: 0 });
						break;
					default:
						console.log("MIDI create Unknown: " + event.cmd);
				}
			}
			addNotes(midi, notePlacement, baseDuration);
			midi.endTrack();
		}

		return midi.getData();
	};

	function addNotes(midi, notePlacement, baseDuration) {
		var times = Object.keys(notePlacement);
		for (var h = 0; h < times.length; h++)
			times[h] = parseFloat(times[h]);
		times.sort(function(a,b) {
			return a - b;
		});
		var lastTime = 0;
		for (var i = 0; i < times.length; i++) {
			var events = notePlacement[times[i]];
			if (times[i] > lastTime) {
				var distance = (times[i] - lastTime) * baseDuration;
				midi.addRest(distance);
				lastTime = times[i];
			}
			for (var j = 0; j < events.length; j++) {
				var event = events[j];
				if (event.volume) {
					midi.startNote(event.pitch, event.volume, event.cents);
				} else {
					midi.endNote(event.pitch);
				}
			}
		}
	}

})();

module.exports = create;