Skip to content

Commit c6f7282

Browse files
authored
add velocity editor and asset controls to piano roll (#11288)
* measure indicator * velocity editor * rename file and save more state * coat of paint * gallery myassets * asset name plus done button
1 parent 73315c5 commit c6f7282

18 files changed

Lines changed: 793 additions & 106 deletions

localtypings/pxtmusic.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ declare namespace pxt.assets.music {
4545
notes: Note[];
4646
startTick: number;
4747
endTick: number;
48+
velocity?: number;
4849
}
4950

5051
export interface Note {

pxtlib/music.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace pxt.assets.music {
3232
* 5 measures
3333
* 6 number of tracks
3434
* ...tracks
35+
* ...track velocities
3536
*
3637
* track(6 + instrument length + note length bytes)
3738
* 0 id
@@ -83,13 +84,24 @@ namespace pxt.assets.music {
8384
* 0 = normal
8485
* 1 = flat
8586
* 2 = sharp
87+
*
88+
* track velocity
89+
* 0 track id
90+
* 1...velocities
91+
*
92+
* velocty
93+
* 1 byte
8694
*/
8795

8896
function encodeSong(song: Song) {
8997
const encodedTracks = song.tracks
9098
.filter((track) => track.notes.length > 0)
9199
.map(encodeTrack);
92-
const trackLength = encodedTracks.reduce((d, c) => c.length + d, 0);
100+
const encodedTrackVelocities = song.tracks
101+
.map(encodeTrackVelocity)
102+
.filter((v): v is Uint8Array => !!v);
103+
104+
const trackLength = (encodedTracks.concat(encodedTrackVelocities)).reduce((d, c) => c.length + d, 0);
93105

94106
const out = new Uint8Array(7 + trackLength);
95107
out[0] = 0; // encoding version
@@ -105,6 +117,10 @@ namespace pxt.assets.music {
105117
current += track.length;
106118
}
107119

120+
for (const trackVelocity of encodedTrackVelocities) {
121+
out.set(trackVelocity, current);
122+
current += trackVelocity.length;
123+
}
108124
return out;
109125
}
110126

@@ -181,6 +197,17 @@ namespace pxt.assets.music {
181197
return encodeMelodicTrack(track);
182198
}
183199

200+
function encodeTrackVelocity(track: Track) {
201+
if (!track.notes.some(note => note.velocity !== undefined && note.velocity < 128)) return undefined;
202+
203+
const out = new Uint8Array(1 + track.notes.length);
204+
out[0] = track.id;
205+
for (let i = 0; i < track.notes.length; i++) {
206+
out[1 + i] = track.notes[i].velocity || 0;
207+
}
208+
return out;
209+
}
210+
184211
function encodeMelodicTrack(track: Track) {
185212
const encodedInstrument = encodeInstrument(track.instrument);
186213
const encodedNotes = track.notes.map(note => encodeNoteEvent(note, track.instrument.octave, false));
@@ -248,14 +275,19 @@ namespace pxt.assets.music {
248275
tracks: []
249276
};
250277

278+
const numTracks = buf[6];
251279
let current = 7;
252280

253-
while (current < buf.length) {
281+
for (let i = 0; i < numTracks; i++) {
254282
const [track, pointer] = decodeTrack(buf, current);
255283
current = pointer;
256284
res.tracks.push(track);
257285
}
258286

287+
while (current < buf.length) {
288+
current = decodeTrackVelocity(buf, res.tracks, current);
289+
}
290+
259291
return res;
260292
}
261293

@@ -296,6 +328,16 @@ namespace pxt.assets.music {
296328
return decodeMelodicTrack(buf, offset);
297329
}
298330

331+
function decodeTrackVelocity(buf: Uint8Array, tracks: Track[], offset: number): number {
332+
const trackId = buf[offset];
333+
const track = tracks.find(t => t.id === trackId);
334+
if (!track) throw new Error(`Track with id ${trackId} not found`);
335+
for (let i = 0; i < track.notes.length; i++) {
336+
track.notes[i].velocity = buf[offset + i + 1];
337+
}
338+
return offset + track.notes.length + 1;
339+
}
340+
299341
function decodeDrumInstrument(buf: Uint8Array, offset: number): DrumInstrument {
300342
const res: DrumInstrument = {
301343
startFrequency: get16BitNumber(buf, offset + 1),

pxtsim/sound/sequencer.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,17 +160,17 @@ namespace pxsim.music {
160160
this.spatialAudioPlayer = player;
161161
}
162162

163-
protected getMelodicTrackVolume(trackIndex: number) {
163+
protected getMelodicTrackVolume(trackIndex: number, velocity: number) {
164164
let trackVolume = 1024;
165165
if (trackIndex < this.trackVolumes.length) {
166166
trackVolume = this.trackVolumes[trackIndex];
167167
}
168168

169-
return this.globalVolume * (trackVolume / 1024);
169+
return this.globalVolume * (trackVolume / 1024) * (velocity / 128);
170170
}
171171

172-
protected getDrumTrackVolume(trackIndex: number, drumIndex: number) {
173-
const trackVolume = this.getMelodicTrackVolume(trackIndex);
172+
protected getDrumTrackVolume(trackIndex: number, drumIndex: number, velocity: number) {
173+
const trackVolume = this.getMelodicTrackVolume(trackIndex, velocity);
174174
let drumVolume = 1024;
175175

176176
if (trackIndex < this.drumTrackVolumes.length && drumIndex < this.drumTrackVolumes[trackIndex].length) {
@@ -200,12 +200,14 @@ namespace pxsim.music {
200200
for (const noteEvent of track.notes) {
201201
if (noteEvent.startTick === this._currentTick) {
202202
for (const note of noteEvent.notes) {
203+
const velocity = noteEvent.velocity ?? 128;
204+
203205
if (this.spatialAudioPlayer) {
204206
if (track.drums) {
205207
playDrumAtSpatialAudioPlayerAsync(
206208
this.spatialAudioPlayer,
207209
track.drums[note.note],
208-
this.getDrumTrackVolume(i, note.note)
210+
this.getDrumTrackVolume(i, note.note, velocity)
209211
);
210212
}
211213
else {
@@ -214,16 +216,16 @@ namespace pxsim.music {
214216
note.note,
215217
track.instrument,
216218
tickToMs(this.currentlyPlaying.beatsPerMinute, this.currentlyPlaying.ticksPerBeat, noteEvent.endTick - noteEvent.startTick),
217-
this.getMelodicTrackVolume(i)
219+
this.getMelodicTrackVolume(i, velocity)
218220
);
219221
}
220222
}
221223
else {
222224
if (track.drums) {
223-
playDrumAsync(track.drums[note.note], () => currentToken.cancelled, this.getDrumTrackVolume(i, note.note));
225+
playDrumAsync(track.drums[note.note], () => currentToken.cancelled, this.getDrumTrackVolume(i, note.note, velocity));
224226
}
225227
else {
226-
playNoteAsync(note.note, track.instrument, tickToMs(this.currentlyPlaying.beatsPerMinute, this.currentlyPlaying.ticksPerBeat, noteEvent.endTick - noteEvent.startTick), () => currentToken.cancelled, this.getMelodicTrackVolume(i));
228+
playNoteAsync(note.note, track.instrument, tickToMs(this.currentlyPlaying.beatsPerMinute, this.currentlyPlaying.ticksPerBeat, noteEvent.endTick - noteEvent.startTick), () => currentToken.cancelled, this.getMelodicTrackVolume(i, velocity));
227229
}
228230
}
229231
}

pxtsim/sound/song.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,14 +272,19 @@ namespace pxsim.music {
272272
tracks: []
273273
};
274274

275+
const numTracks = buf[6];
275276
let current = 7;
276277

277-
while (current < buf.length) {
278+
for (let i = 0; i < numTracks; i++) {
278279
const [track, pointer] = decodeTrack(buf, current);
279280
current = pointer;
280281
res.tracks.push(track);
281282
}
282283

284+
while (current < buf.length) {
285+
current = decodeTrackVelocity(buf, res.tracks, current);
286+
}
287+
283288
return res;
284289
}
285290

@@ -320,6 +325,17 @@ namespace pxsim.music {
320325
return decodeMelodicTrack(buf, offset);
321326
}
322327

328+
function decodeTrackVelocity(buf: Uint8Array, tracks: pxt.assets.music.Track[], offset: number): number {
329+
const trackId = buf[offset];
330+
const track = tracks.find(t => t.id === trackId);
331+
if (!track) return buf.length;
332+
333+
for (let i = 0; i < track.notes.length; i++) {
334+
track.notes[i].velocity = buf[offset + i + 1];
335+
}
336+
return offset + track.notes.length + 1;
337+
}
338+
323339
function decodeDrumInstrument(buf: Uint8Array, offset: number): pxt.assets.music.DrumInstrument {
324340
const res: pxt.assets.music.DrumInstrument = {
325341
startFrequency: get16BitNumber(buf, offset + 1),

0 commit comments

Comments
 (0)