mirror of
https://github.com/ChomeNS/chomens-bot-mc.git
synced 2024-11-14 10:44:55 -05:00
add nbs and txt support for music
This commit is contained in:
parent
e78cda6373
commit
94517b0a63
7 changed files with 273 additions and 9 deletions
|
@ -3,5 +3,6 @@
|
||||||
"1.19 Support",
|
"1.19 Support",
|
||||||
"*music play file autocomplete",
|
"*music play file autocomplete",
|
||||||
"Improved URL loading in *draw and *music playurl",
|
"Improved URL loading in *draw and *music playurl",
|
||||||
"You can now do *clearchat [player] without \"specific\""
|
"You can now do *clearchat [player] without \"specific\"",
|
||||||
|
"Added .nbs and .txt support for music command"
|
||||||
]
|
]
|
|
@ -3,6 +3,7 @@
|
||||||
const fs = require('fs/promises')
|
const fs = require('fs/promises')
|
||||||
const { EmbedBuilder } = require('discord.js')
|
const { EmbedBuilder } = require('discord.js')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const getFilenameFromUrl = require('../util/getFilenameFromUrl')
|
||||||
const fileExists = require('../util/file-exists')
|
const fileExists = require('../util/file-exists')
|
||||||
const fileList = require('../util/file-list')
|
const fileList = require('../util/file-list')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
|
@ -31,7 +32,7 @@ async function play (bot, values, discord, channeldc, selector, config) {
|
||||||
filepath !== '') absolutePath = await resolve(file)
|
filepath !== '') absolutePath = await resolve(file)
|
||||||
else absolutePath = await resolve(filepath)
|
else absolutePath = await resolve(filepath)
|
||||||
|
|
||||||
song = bot.music.load(await fs.readFile(absolutePath), path.basename(absolutePath))
|
song = await bot.music.load(await fs.readFile(absolutePath), path.basename(absolutePath))
|
||||||
bot.music.queue.push(song)
|
bot.music.queue.push(song)
|
||||||
bot.music.play(song)
|
bot.music.play(song)
|
||||||
if (discord) {
|
if (discord) {
|
||||||
|
@ -44,6 +45,7 @@ async function play (bot, values, discord, channeldc, selector, config) {
|
||||||
bot.tellraw(selector, [{ text: 'Added ', color: 'white' }, { text: song.name, color: 'gold' }, { text: ' to the song queue', color: 'white' }])
|
bot.tellraw(selector, [{ text: 'Added ', color: 'white' }, { text: song.name, color: 'gold' }, { text: ' to the song queue', color: 'white' }])
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
if (discord) {
|
if (discord) {
|
||||||
const Embed = new EmbedBuilder()
|
const Embed = new EmbedBuilder()
|
||||||
.setColor(config.discord.embedsColors.error)
|
.setColor(config.discord.embedsColors.error)
|
||||||
|
@ -66,7 +68,7 @@ async function playUrl (bot, values, discord, channeldc, selector, config) {
|
||||||
},
|
},
|
||||||
responseType: 'arraybuffer'
|
responseType: 'arraybuffer'
|
||||||
})
|
})
|
||||||
song = bot.music.load(response.data, url)
|
song = await bot.music.load(response.data, getFilenameFromUrl(url))
|
||||||
bot.music.queue.push(song)
|
bot.music.queue.push(song)
|
||||||
bot.music.play(song)
|
bot.music.play(song)
|
||||||
if (discord) {
|
if (discord) {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
const path = require('path')
|
||||||
const { Midi } = require('@tonejs/midi')
|
const { Midi } = require('@tonejs/midi')
|
||||||
const { convertMidi } = require('../util/midi_converter')
|
const { convertMidi } = require('../util/midi_converter')
|
||||||
|
const convertNBS = require('../util/nbs_converter')
|
||||||
|
const parseTXTSong = require('../util/txt_song_parser')
|
||||||
|
|
||||||
const soundNames = {
|
const soundNames = {
|
||||||
harp: 'minecraft:block.note_block.harp',
|
harp: 'minecraft:block.note_block.harp',
|
||||||
|
@ -79,11 +81,22 @@ function inject (bot) {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
})
|
})
|
||||||
|
|
||||||
bot.music.load = function (buffer, fallbackName = '[unknown]') {
|
bot.music.load = async function (buffer, fallbackName = '[unknown]') {
|
||||||
// TODO: NBS Support
|
let song
|
||||||
const midi = new Midi(buffer)
|
switch (path.extname(fallbackName)) {
|
||||||
const song = convertMidi(midi)
|
case '.nbs':
|
||||||
if (song.name === '') song.name = fallbackName
|
song = convertNBS(buffer)
|
||||||
|
break
|
||||||
|
case '.txt':
|
||||||
|
song = parseTXTSong(buffer.toString())
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const midi = new Midi(buffer)
|
||||||
|
song = convertMidi(midi)
|
||||||
|
if (song.name === '') song.name = fallbackName
|
||||||
|
break
|
||||||
|
}
|
||||||
return song
|
return song
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
util/getFilenameFromUrl.js
Normal file
14
util/getFilenameFromUrl.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
const path = require('path')
|
||||||
|
/**
|
||||||
|
* get filename from url
|
||||||
|
* @param {string} urlStr the url
|
||||||
|
* @return {string} filename
|
||||||
|
* @example
|
||||||
|
* getFilenameFromUrl('https://sus.red/amogus.mid?verysus=true') // returns 'amogus.mid'
|
||||||
|
*/
|
||||||
|
function getFilenameFromUrl (urlStr) {
|
||||||
|
const url = new URL(urlStr)
|
||||||
|
return path.basename(url.pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getFilenameFromUrl
|
62
util/nbs_converter.js
Normal file
62
util/nbs_converter.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
const nbs = require('./nbs_file')
|
||||||
|
const instrumentNames = [
|
||||||
|
'harp',
|
||||||
|
'bass',
|
||||||
|
'basedrum',
|
||||||
|
'snare',
|
||||||
|
'hat',
|
||||||
|
'guitar',
|
||||||
|
'flute',
|
||||||
|
'bell',
|
||||||
|
'chime',
|
||||||
|
'xylophone',
|
||||||
|
'iron_xylophone',
|
||||||
|
'cow_bell',
|
||||||
|
'didgeridoo',
|
||||||
|
'bit',
|
||||||
|
'banjo',
|
||||||
|
'pling'
|
||||||
|
]
|
||||||
|
|
||||||
|
function convertNBS (buf) {
|
||||||
|
const parsed = nbs.parse(buf)
|
||||||
|
const song = {
|
||||||
|
name: parsed.songName,
|
||||||
|
notes: [],
|
||||||
|
loop: false,
|
||||||
|
loopPosition: 0,
|
||||||
|
length: 0
|
||||||
|
}
|
||||||
|
if (parsed.loop > 0) {
|
||||||
|
song.loop = true
|
||||||
|
song.loopPosition = parsed.loopStartTick
|
||||||
|
}
|
||||||
|
for (const note of parsed.nbsNotes) {
|
||||||
|
let instrument = note.instrument
|
||||||
|
if (note.instrument < instrumentNames.length) {
|
||||||
|
instrument = instrumentNames[note.instrument]
|
||||||
|
} else continue
|
||||||
|
|
||||||
|
if (note.key < 33 || note.key > 55) continue
|
||||||
|
|
||||||
|
const layerVolume = 100
|
||||||
|
// will add layer volume later
|
||||||
|
|
||||||
|
const time = tickToMs(note.tick, parsed.tempo)
|
||||||
|
song.length = Math.max(song.length, time)
|
||||||
|
|
||||||
|
song.notes.push({
|
||||||
|
instrument,
|
||||||
|
pitch: note.key - 33,
|
||||||
|
volume: note.velocity * layerVolume / 10000,
|
||||||
|
time
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return song
|
||||||
|
}
|
||||||
|
|
||||||
|
function tickToMs (tick = 1, tempo) {
|
||||||
|
return Math.floor(1000 * tick * 100 / tempo)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = convertNBS
|
157
util/nbs_file.js
Normal file
157
util/nbs_file.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
function parse (buffer) {
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
let songLength = 0
|
||||||
|
let format = 0
|
||||||
|
let vanillaInstrumentCount = 0
|
||||||
|
songLength = readShort()
|
||||||
|
if (songLength === 0) {
|
||||||
|
format = readByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format >= 1) {
|
||||||
|
vanillaInstrumentCount = readByte()
|
||||||
|
}
|
||||||
|
if (format >= 3) {
|
||||||
|
songLength = readShort()
|
||||||
|
}
|
||||||
|
|
||||||
|
const layerCount = readShort()
|
||||||
|
const songName = readString()
|
||||||
|
const songAuthor = readString()
|
||||||
|
const songOriginalAuthor = readString()
|
||||||
|
const songDescription = readString()
|
||||||
|
const tempo = readShort()
|
||||||
|
const autoSaving = readByte()
|
||||||
|
const autoSavingDuration = readByte()
|
||||||
|
const timeSignature = readByte()
|
||||||
|
const minutesSpent = readInt()
|
||||||
|
const leftClicks = readInt()
|
||||||
|
const rightClicks = readInt()
|
||||||
|
const blocksAdded = readInt()
|
||||||
|
const blocksRemoved = readInt()
|
||||||
|
const origFileName = readString()
|
||||||
|
|
||||||
|
let loop = 0
|
||||||
|
let maxLoopCount = 0
|
||||||
|
let loopStartTick = 0
|
||||||
|
if (format >= 4) {
|
||||||
|
loop = readByte()
|
||||||
|
maxLoopCount = readByte()
|
||||||
|
loopStartTick = readShort()
|
||||||
|
}
|
||||||
|
|
||||||
|
const nbsNotes = []
|
||||||
|
let tick = -1
|
||||||
|
while (true) {
|
||||||
|
const tickJumps = readShort()
|
||||||
|
if (tickJumps === 0) break
|
||||||
|
tick += tickJumps
|
||||||
|
|
||||||
|
let layer = -1
|
||||||
|
while (true) {
|
||||||
|
const layerJumps = readShort()
|
||||||
|
if (layerJumps === 0) break
|
||||||
|
layer += layerJumps
|
||||||
|
const note = nbsNote()
|
||||||
|
note.tick = tick
|
||||||
|
note.layer = layer
|
||||||
|
note.instrument = readByte()
|
||||||
|
note.key = readByte()
|
||||||
|
if (format >= 4) {
|
||||||
|
note.velocity = readByte()
|
||||||
|
note.panning = readByte()
|
||||||
|
note.pitch = readShort()
|
||||||
|
}
|
||||||
|
nbsNotes.push(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nbsLayers = []
|
||||||
|
if (i <= buffer.length) {
|
||||||
|
for (let j = 0; j < layerCount; j++) {
|
||||||
|
const layer = nbsLayer()
|
||||||
|
layer.name = readString()
|
||||||
|
if (format >= 4) {
|
||||||
|
layer.lock = readByte()
|
||||||
|
}
|
||||||
|
layer.volume = readByte()
|
||||||
|
if (format >= 2) {
|
||||||
|
layer.stereo = readByte()
|
||||||
|
}
|
||||||
|
nbsLayers.push(layer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
songLength,
|
||||||
|
format,
|
||||||
|
vanillaInstrumentCount,
|
||||||
|
layerCount,
|
||||||
|
songName,
|
||||||
|
songAuthor,
|
||||||
|
songOriginalAuthor,
|
||||||
|
songDescription,
|
||||||
|
tempo,
|
||||||
|
autoSaving,
|
||||||
|
autoSavingDuration,
|
||||||
|
timeSignature,
|
||||||
|
minutesSpent,
|
||||||
|
leftClicks,
|
||||||
|
rightClicks,
|
||||||
|
blocksAdded,
|
||||||
|
blocksRemoved,
|
||||||
|
origFileName,
|
||||||
|
loop,
|
||||||
|
maxLoopCount,
|
||||||
|
loopStartTick,
|
||||||
|
nbsNotes,
|
||||||
|
nbsLayers
|
||||||
|
}
|
||||||
|
|
||||||
|
function readByte () {
|
||||||
|
return buffer.readInt8(i++)
|
||||||
|
}
|
||||||
|
|
||||||
|
function readShort () {
|
||||||
|
const short = buffer.readInt16LE(i)
|
||||||
|
i += 2
|
||||||
|
return short
|
||||||
|
}
|
||||||
|
|
||||||
|
function readInt () {
|
||||||
|
const int = buffer.readInt32LE(i)
|
||||||
|
i += 4
|
||||||
|
return int
|
||||||
|
}
|
||||||
|
|
||||||
|
function readString () {
|
||||||
|
let length = readInt()
|
||||||
|
let string = ''
|
||||||
|
for (; length > 0; length--) string += String.fromCharCode(readByte())
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nbsNote () {
|
||||||
|
return {
|
||||||
|
tick: null,
|
||||||
|
layer: null,
|
||||||
|
instrument: null,
|
||||||
|
key: null,
|
||||||
|
velocity: 100,
|
||||||
|
panning: 100,
|
||||||
|
pitch: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nbsLayer () {
|
||||||
|
return {
|
||||||
|
name: null,
|
||||||
|
lock: 0,
|
||||||
|
volume: null,
|
||||||
|
stereo: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { parse, nbsNote, nbsLayer }
|
15
util/txt_song_parser.js
Normal file
15
util/txt_song_parser.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const { instrumentsArray } = require('minecraft-data')('1.15.2') // chip hardcoding moment
|
||||||
|
|
||||||
|
function parseTXTSong (data) {
|
||||||
|
let length = 0
|
||||||
|
const notes = String(data).split(/\r\n|\r|\n/).map(line => {
|
||||||
|
const [tick, pitch, instrument] = line.split(':').map(Number)
|
||||||
|
if (tick === undefined || pitch === undefined || instrument === undefined) return undefined
|
||||||
|
const time = tick * 50
|
||||||
|
length = Math.max(length, time)
|
||||||
|
return { time, pitch, instrument: instrumentsArray[instrument].name, volume: 1 }
|
||||||
|
}).filter(note => note !== undefined)
|
||||||
|
return { name: '', notes, loop: false, loopPosition: 0, length }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parseTXTSong
|
Loading…
Reference in a new issue