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",
|
||||
"*music play file autocomplete",
|
||||
"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 { EmbedBuilder } = require('discord.js')
|
||||
const path = require('path')
|
||||
const getFilenameFromUrl = require('../util/getFilenameFromUrl')
|
||||
const fileExists = require('../util/file-exists')
|
||||
const fileList = require('../util/file-list')
|
||||
const axios = require('axios')
|
||||
|
@ -31,7 +32,7 @@ async function play (bot, values, discord, channeldc, selector, config) {
|
|||
filepath !== '') absolutePath = await resolve(file)
|
||||
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.play(song)
|
||||
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' }])
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
if (discord) {
|
||||
const Embed = new EmbedBuilder()
|
||||
.setColor(config.discord.embedsColors.error)
|
||||
|
@ -66,7 +68,7 @@ async function playUrl (bot, values, discord, channeldc, selector, config) {
|
|||
},
|
||||
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.play(song)
|
||||
if (discord) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
const path = require('path')
|
||||
const { Midi } = require('@tonejs/midi')
|
||||
const { convertMidi } = require('../util/midi_converter')
|
||||
const convertNBS = require('../util/nbs_converter')
|
||||
const parseTXTSong = require('../util/txt_song_parser')
|
||||
|
||||
const soundNames = {
|
||||
harp: 'minecraft:block.note_block.harp',
|
||||
|
@ -79,11 +81,22 @@ function inject (bot) {
|
|||
clearInterval(interval)
|
||||
})
|
||||
|
||||
bot.music.load = function (buffer, fallbackName = '[unknown]') {
|
||||
// TODO: NBS Support
|
||||
const midi = new Midi(buffer)
|
||||
const song = convertMidi(midi)
|
||||
if (song.name === '') song.name = fallbackName
|
||||
bot.music.load = async function (buffer, fallbackName = '[unknown]') {
|
||||
let song
|
||||
switch (path.extname(fallbackName)) {
|
||||
case '.nbs':
|
||||
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
|
||||
}
|
||||
|
||||
|
|
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