chipmunkbot-archive/plugins/music.js
2022-11-13 01:32:35 +00:00

214 lines
6.5 KiB
JavaScript

const fs = require('fs')
const path = require('path')
const { Midi } = require('@tonejs/midi')
const { convertMidi } = require('../util/midi_converter')
const convertNBS = require('../util/nbs-converter.js')
const parseTXTSong = require('../util/txt-song-parser')
const { instruments } = require('minecraft-data')('1.15.2') // fard
const soundMap = {
harp: 'block.note_block.harp',
basedrum: 'block.note_block.basedrum',
snare: 'block.note_block.snare',
hat: 'block.note_block.hat',
bass: 'block.note_block.bass',
flute: 'block.note_block.flute',
bell: 'block.note_block.bell',
guiter: 'block.note_block.guiter',
chime: 'block.note_block.chime',
xylophone: 'block.note_block.xylophone',
iron_xylophone: 'block.note_block.iron_xylophone',
cow_bell: 'block.note_block.cow_bell',
didgeridoo: 'block.note_block.didgeridoo',
bit: 'block.note_block.bit',
banjo: 'block.note_block.banjo',
pling: 'block.note_block.pling'
}
const oldSoundMap = {
harp: 'block.note_block.harp',
basedrum: 'block.note_block.basedrum',
snare: 'block.note_block.snare',
hat: 'block.note_block.hat',
bass: 'block.note_block.bass',
flute: 'block.note_block.harp',
bell: 'block.note_block.harp', // 'entity.experience_orb.pickup'
guiter: 'block.note_block.bass',
chime: 'block.note_block.harp',
xylophone: 'block.note_block.harp',
iron_xylophone: 'block.note_block.harp',
cow_bell: 'block.note_block.cow_bell',
didgeridoo: 'block.note_block.bass',
bit: 'block.note_block.harp',
banjo: 'block.note_block.bass',
pling: 'block.note_block.pling'
}
function inject (bot) {
bot.music = {
playing: false,
queue: [],
nowPlaying: undefined,
looping: false,
_interval: null,
_playNextSong,
skip,
stop,
play,
normalize
}
bot.music.nowPlaying = {
name: '',
tick: {
current: null,
total: null
// npt: null
}
}
setInterval(() => {
if (!bot.music.playing) return
const msg = [
{ text: 'Now Playing', color: bot.colors.primary },
{ text: ' | ', color: 'dark_gray' },
{ text: bot.music.nowPlaying.displayName, color: bot.colors.secondary, bold: true },
{ text: ' | ', color: 'dark_gray' },
format(bot.music.nowPlaying.time),
{ text: ' / ', color: 'gray' },
format(bot.music.nowPlaying.length)
// { text: ' (', color: 'dark_gray' },
// bot.music.nowPlaying.note.npt + ' npt',
// { text: ')', color: 'dark_gray' }
]
if (bot.music.looping) {
msg.push({ text: ' | ', color: 'dark_gray' })
msg.push({ text: 'Looping', color: bot.colors.secondary })
}
bot.core.run('/title @a actionbar ' + JSON.stringify(msg))
}, 500)
function _playNextSong () {
const song = bot.music.queue.shift()
if (song != null) play(song)
}
function skip () {
clearInterval(bot.music._interval)
bot.music.playing = false
if (bot.music.queue.length !== 0) _playNextSong()
}
function stop () {
bot.music.queue = []
clearInterval(bot.music._interval)
bot.music.playing = false
}
function play (filepath) {
// set stuff up
filepath = path.resolve(filepath)
let song
try {
switch (path.extname(filepath)) {
case '.nbs':
song = convertNBS(fs.readFileSync(filepath))
break
case '.js':
case '.cjs':
case '.json':
const requirePath = require.resolve(filepath)
delete require.cache[requirePath]
song = normalize(require(requirePath))
break
case '.txt':
song = parseTXTSong(fs.readFileSync(filepath).toString())
break
default:
const midi = new Midi(fs.readFileSync(filepath))
song = normalize(convertMidi(midi))
}
song.displayName = song.name.length > 0 ? `${song.name} (${path.basename(filepath)})` : path.basename(filepath)
song.time = 0
bot.music.nowPlaying = song
} catch (err) {
bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: err?.message, color: bot.colors.error }))
return
}
// play the music lol
bot.core.run(`/tellraw @a ${JSON.stringify([
{ text: 'Now playing ', color: bot.colors.primary },
{ text: song.displayName, color: bot.colors.secondary },
])}`)
bot.music.playing = true
bot.music.looping = song.loop
let startTime = Date.now()
bot.music._interval = setInterval(() => {
const intervalTime = song.time + 1
song.time = Date.now() - startTime
song.notes.forEach((note, i) => {
const _time = note.time // Math.floor(note.time)
if (intervalTime <= _time && song.time >= _time) {
const sound = soundMap[note.instrument]
// const oldSound = oldSoundMap[note.instrument]
const floatingpitch = Math.pow(2, (note.pitch - 12) / 12.0)
bot.core.run(`minecraft:execute as @a[tag=!nomusic] at @s run playsound ${sound} record @s ^ ^ ^ ${note.volume} ${floatingpitch}`)
}
})
if (song.time > song.length) {
if (bot.music.looping) {
startTime = Date.now() + song.loopPosition
return
}
clearInterval(bot.music._interval)
bot.music.playing = false
bot.core.run('/tellraw @a ' + JSON.stringify([
{ text: 'Finished playing ', color: 'green' },
{ text: bot.music.nowPlaying.name, color: 'dark_green' }
]))
if (bot.music.queue.length !== 0) _playNextSong()
}
}, 1)
}
}
function normalize (song) {
const normalizeNote = note => { // change notes with tick to notes with time
if (note.time == null && note.tick != null) {
note.time = note.tick * 50
delete note.tick
}
const num = Number(note.instrument)
if (!Number.isNaN(num)) note.instrument = instruments[num].name
}
if (Array.isArray(song)) { // if the song is actually an array, convert it to a song
let length = 0
for (const note of song) {
normalizeNote(note)
length = Math.max(note.time, length)
}
return { name: '', notes: song, loop: false, loopPosition: 0, length }
}
let length = 0
for (const note of song.notes ?? []) {
normalizeNote(note)
length = Math.max(note.time, length)
}
song.length = length
return { name: '', notes: [], loop: false, loopPosition: 0, length, ...song }
}
function format (ms) {
const s = ms / 1000
const seconds = Math.floor(s / 60).toString()
const minutes = Math.floor(s % 60).toString()
return seconds + ':' + (minutes.length <= 1 ? '0' : '') + minutes
}
module.exports.bot = inject