UNPKG

tuneflow-plugin-basic

Version:
189 lines (176 loc) 5.53 kB
import type { ParamDescriptor, SelectWidgetConfig, SelectWidgetOption, Song, SwitchWidgetConfig, } from 'tuneflow'; import { TuneflowPlugin, WidgetType, TrackType } from 'tuneflow'; import _ from 'underscore'; // Dynamic import all presets since they are pretty large in size. const musicgatewayPresets = () => import('./add_drum_track_plugin_presets/musicgateway.com'); const drumPatterns = { musicgateway: { credit: 'musicgateway.com', patterns: musicgatewayPresets, }, }; export class AddDrumTrack extends TuneflowPlugin { private drumOptions: SelectWidgetOption[] = []; static LOAD_PRESETS_PROMISE: Promise<void> | null = null; static PRESETS_LOADED = false; static providerId(): string { return 'andantei'; } static pluginId(): string { return 'add-drum-track'; } async init(): Promise<void> { if (!AddDrumTrack.LOAD_PRESETS_PROMISE) { AddDrumTrack.LOAD_PRESETS_PROMISE = new Promise((resolve, reject) => { Promise.all( _.keys(drumPatterns).map(async key => { const drumPatternsEntry = (drumPatterns as any)[key]; drumPatternsEntry.patterns = (await drumPatternsEntry.patterns()).preset; }), ).then( () => { AddDrumTrack.PRESETS_LOADED = true; resolve(); }, () => { reject('Load drum track failed.'); }, ); }); } try { await AddDrumTrack.LOAD_PRESETS_PROMISE; } catch (e) { console.error(e); } if (AddDrumTrack.PRESETS_LOADED) { this.drumOptions = _.flatten( _.keys(drumPatterns).map(drumPatternsKey => { const patternsConfig = (drumPatterns as any)[drumPatternsKey]; return patternsConfig.patterns.map((item: any, index: number) => { console.log(item); return { label: item[0], value: `${drumPatternsKey}~_~${index}`, }; }); }), ); } } params(): { [paramName: string]: ParamDescriptor } { return { preset: { displayName: { zh: '鼓点类型', en: 'Pattern', }, description: { zh: '制作: musicgateway.com', en: 'Credit: musicgateway.com', }, defaultValue: undefined, widget: { type: WidgetType.Select, config: { options: this.drumOptions, placeholder: { zh: '选择鼓点类型', en: 'Select a drum pattern', }, allowSearch: true, } as SelectWidgetConfig, }, }, loopPattern: { displayName: { zh: '循环', en: 'Loop', }, description: { zh: '是否循环鼓点片段直到歌曲结尾', en: 'Whether to repeat the pattern until the end of the song', }, defaultValue: false, widget: { type: WidgetType.Switch, config: {} as SwitchWidgetConfig, }, }, }; } async run(song: Song, params: { [paramName: string]: any }): Promise<void> { const presetKey = this.getParam<string>(params, 'preset'); const loopPattern = this.getParam<boolean>(params, 'loopPattern'); const presetKeyParts = presetKey.split('~_~'); const drumPatternsGroup = presetKeyParts[0]; const drumPatternIndex = Number(presetKeyParts[1]); const drumPattern = (drumPatterns as any)[drumPatternsGroup].patterns[drumPatternIndex][1]; const newTrack = song.createTrack({ type: TrackType.MIDI_TRACK, index: song.getTracks().length, }); newTrack.setInstrument({ program: 0, isDrum: true, }); let firstNoteTick = Number.MAX_VALUE; for (const track of song.getTracks()) { if (track.getClips().length > 0 && track.getClips()[0].getNotes().length > 0) { firstNoteTick = Math.min(track.getClips()[0].getNotes()[0].getStartTick(), firstNoteTick); } } const lastTick = song.getLastTick(); const newClip = newTrack.createMIDIClip({ clipStartTick: firstNoteTick, clipEndTick: lastTick, }); let lastEndTick = firstNoteTick; let reachedEnd = false; while (!reachedEnd) { const offset = lastEndTick; for (const track of drumPattern.tracks) { // This track.notes is an exception: // We use a special format to save drum patterns, // which simplifies track.clips.notes to track.notes. for (const note of track.notes) { const pitch = note[1]; const velocity = Math.max(64, note[3]); const startTicksRaw = note[0]; const endTicksRaw = note[0] + note[2]; const startTick = offset + (startTicksRaw / drumPattern.ppq) * song.getResolution(); if (startTick >= lastTick) { reachedEnd = true; break; } const endTick = offset + (endTicksRaw / drumPattern.ppq) * song.getResolution(); lastEndTick = Math.max(lastEndTick, endTick); const noteParam = { pitch, velocity, startTick, endTick, }; newClip.createNote(noteParam); } if (reachedEnd) { break; } } if (!loopPattern) { // Don't loop until the end. break; } if (lastEndTick <= offset) { // offset is not increasing. break; } } } }