npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@osbjs/tiny-osbjs

v3.2.0

Published

a declarative osu! storyboard library

Downloads

30

Readme

tiny-osbjs

A declarative osu! storyboard library with zero dependencies and zero configurations.

Install

npm i @osbjs/tiny-osbjs@latest

Bootstrap your project using create-tiny-sb

The recommended way to create a storyboard project is using create-tiny-sb:

npm create tiny-sb

It also comes with some prebuilt components!

Setup your project manually

It's strongly recommended to use TypeScript and a text editor/IDE with good TypeScript support like VSCode for better developing experience.

npm install -D typescript

Install tsx as a global package so you can use it anywhere (you will only need to do this once), or as an devDependency. tsx is a CLI command (alternative to node) that allows you to run TypeScript/JavaScript right in the terminal. It also watches for file changes to speed up your development.

npm i -g tsx

or

npm i -D tsx

Then add this script to your package.json:

//...
"scripts": {
	"start": "tsx watch index.js" 
	// or if you are using TypeScript
	"start": "tsx watch index.ts" 
}

index.js (index.ts) is the entry point to your storyboard. This way when you run npm start it will automatically rebuild your storyboard when you change your code.

Usage

Note: The example below will be written in TypeScript/ES Module syntax.

Before you do anything, you have to create a storyboard context and tell the library to use it. This context is shared accross the whole project.

import { createContext, useContext } from '@osbjs/tiny-osbjs'

const context = createContext()
useContext(context)

Then you can start creating storyboard objects.

import { createSprite, Layer, Origin } from 'tiny-osbjs'

createSprite('test.png', Layer.Background, Origin.Centre, [320, 240], () => {})

If you want to create a storyboard for each difficulty, you must specify the context at the entry point of each storyboard.

// difficulty1.ts
import { createContext, useContext } from '@osbjs/tiny-osbjs'

export default function difficulty1Storyboard() {
	const context = createContext()
	useContext(context)

	// createSprite...
}

// difficulty2.ts
import { createContext, useContext } from '@osbjs/tiny-osbjs'

export default function Difficulty2() {
	const context = createContext()
	useContext(context)

	// createSprite...
}

// index.ts
import Difficulty1 from './difficulty1'
import Difficulty2 from './difficulty2'

difficulty1Storyboard()
difficulty2Storyboard()

Most of the commands will have their syntax looking like this (except for a few special commands):

commandName([startTime, endTime], startValue, endValue, easing) // commands that change overtime
commandName([startTime, endTime], value) // commands that are only effective in a specific duration
commandName(time, value) // commands that only need to be set once

The killer-feature of tiny-osbjs is that you can specify the commands in a declarative way and the library will know which objects they are refering to.

import { createSprite, Layer, Origin, fade, loop } from '@osbjs/tiny-osbjs'

createSprite('test.png', Layer.Background, Origin.Centre, [320, 240], () => {
	fade([0, 1000], 0, 1, Easing.Out) // refers to sprite

	loop(3000, 5, () => {
		fade([0, 1000], 0, 1, Easing.Out) // refers to loop
	})
})

You can pass osu timestamp to the start time/end time of the command and the library will try to parse it.

import { createSprite, Layer, Origin, fade, loop } from '@osbjs/tiny-osbjs'

createSprite('test.png', Layer.Background, Origin.Centre, [320, 240], () => {
	fade([0, "00:00:015"], 0, 1) // this works
})

Even though it isn't enforced, you should split your storyboard into multiple components.

// components/Background.ts
import { createSprite, fade, Layer, Origin } from '@osbjs/tiny-osbjs'

export default function Background(startTime: number, endTime: number) {
	createSprite('bg.jpg', Layer.Background, Origin.Centre, [320, 240], () => {
		fade([startTime, endTime], 1)
	})
}

// index.ts
import Background from './components/Background'

Background(0, 30000)

Finally, you can generate the osb string of the storyboard. You can use that string to write to your osb file.

import { generateStoryboardOsb } from '@osbjs/tiny-osbjs'
import fs from 'fs'

fs.writeFileSync('Artist - Song (Creator).osb', generateStoryboardOsb(), 'utf8')

Your final storyboard will look like this:

// components/Background.ts
import { createSprite, fade, Layer, Origin } from '@osbjs/tiny-osbjs'

export default function Background(startTime: number, endTime: number) {
	createSprite('bg.jpg', Layer.Background, Origin.Centre, [320, 240], () => {
		fade([startTime, endTime], 1)
	})
}

// index.ts
import { createContext, generateStoryboardOsb, useContext } from '@osbjs/tiny-osbjs'
import fs from 'fs'
import Background from './components/Background'

const context = createContext()
useContext(context)

Background(0, 30000)

fs.writeFileSync('Artist - Song (Creator).osb', generateStoryboardOsb(), 'utf8')

If you ran into any issues or need help, contact Nanachi#1381 on discord.

API documentation

Common types

// [r, g, b] respectively
type Color = [number, number, number]

// [x, y] respectively
type Vector2 = [number, number]

// ex: 01:29:345
type Timestamp = `${number}:${number}:${number}`

type Time = Timestamp | number

// represent start time/end time
type TimeRange = [Time, Time]

// osu storyboard layer
enum Layer {
	Background = 'Background',
	Foreground = 'Foreground',
	Fail = 'Fail',
	Pass = 'Pass',
	Overlay = 'Overlay',
}

// origin of the sprite/animation
enum Origin {
	TopLeft = 'TopLeft',
	TopCentre = 'TopCentre',
	TopRight = 'TopRight',
	CentreRight = 'CentreRight',
	Centre = 'Centre',
	CentreLeft = 'CentreLeft',
	BottomLeft = 'BottomLeft',
	BottomCentre = 'BottomCentre',
	BottomRight = 'BottomRight',
}

// see https://easings.net/en
enum Easing {
	Linear,
	Out,
	In,
	InQuad,
	OutQuad,
	InOutQuad,
	InCubic,
	OutCubic,
	InOutCubic,
	InQuart,
	OutQuart,
	InOutQuart,
	InQuint,
	OutQuint,
	InOutQuint,
	InSine,
	OutSine,
	InOutSine,
	InExpo,
	OutExpo,
	InOutExpo,
	InCirc,
	OutCirc,
	InOutCirc,
	InElastic,
	OutElastic,
	OutElasticHalf,
	OutElasticQuarter,
	InOutElastic,
	InBack,
	OutBack,
	InOutBack,
	InBounce,
	OutBounce,
	InOutBounce,
}

createContext

function createContext(): Context

Create a new context.

useContext

function useContext(context: Context)

Specify the context of the storyboard.

createBackground

function createBackground(path: string)

Create a new Background image. You should only use this if you are generating a storyboard for a specific osu difficulty.

createVideo

function createVideo(path: string, offset: number)

Create a new Video. You should only use this if you are generating a storyboard for a specific osu difficulty.

createSample

function createSample(
	startTime: number,
	layer: SampleLayer,
	path: AudioPath,
	volume: number
)

type AudioPath = `${string}.mp3` | `${string}.ogg` | `${string}.wav`

enum SampleLayer {
	Background,
	Fail,
	Pass,
	Foreground,
}

Create a new Sample.

createSprite

function createSprite(
	path: string,
	layer: Layer,
	origin: Origin,
	initialPosition: Vector2,
	invokeFunction: () => void
)

Create a new Sprite. All commands must be called inside the invoke function.

createAnimation

function createAnimation(
	path: string,
	layer: Layer,
	origin: Origin,
	initialPosition: Vector2,
	frameCount: number,
	frameDelay: number,
	loopType: LoopType,
	invokeFunction: () => void
)

enum LoopType {
	Forever = 'LoopForever',
	Once = 'LoopOnce',
}

Create a new Animation. All commands must be called inside the invoke function.

generateStoryboardOsb

function generateStoryboardOsb(): string

Generate string that can be used to create .osb file.

replaceOsuEvents

function replaceOsuEvents(parsedOsuDifficulty: string): string

Returns .osu file after replacing [Events] section with events generated from storyboard.

color

function color(time: Time | TimeRange, startColor: Color, endColor: Color = startColor, easing?: Easing)

The virtual light source colour on the object. The colours of the pixels on the object are determined subtractively.

fade

function fade(time: Time | TimeRange, startOpacity: number, endOpacity: number = startOpacity, easing?: Easing)

Change the opacity of the object.

move

function move(time: Time | TimeRange, startPosition: Vector2, endPosition: Vector2 = startPosition, easing?: Easing)

Change the location of the object in the play area.

moveX

function moveX(time: Time | TimeRange, startX: number, endX: number = startX, easing?: Easing)

Change the x coordinate of the object.

moveY

function moveY(time: Time | TimeRange, startY: number, endY: number = startY, easing?: Easing)

Change the y coordinate of the object.

rotate

function rotate(time: Time | TimeRange, startAngle: number, endAngle: number = startAngle, easing?: Easing)

Change the amount an object is rotated from its original image, in radians, clockwise.

scale

function scale(time: Time | TimeRange, startScaleFactor: number, endScaleFactor: number = startScaleFactor, easing?: Easing)

Change the size of the object relative to its original size.

scaleVec

function scaleVec(time: Time | TimeRange, startScaleVector: Vector2, endScaleVector: Vector2 = startScaleVector, easing?: Easing)

Change the size of the object relative to its original size, but X and Y scale separately.

flipHorizontal

function flipHorizontal(time: TimeRange)

Flip the image horizontally.

flipVertical

function flipVertical(time: TimeRange)

Flip the image vertically.

additiveBlending

function additiveBlending(time: TimeRange)

Use additive-colour blending instead of alpha-blending

loop

function loop(startTime: Time, count: number, invokeFunction: () => void)

Create a loop group.

Loops can be defined to repeat a set of events constantly for a set number of iterations. Note that events inside a loop should be timed with a zero-base. This means that you should start from 0ms for the inner event's timing and work up from there. The loop event's start time will be added to this value at game runtime.

trigger

function trigger(time: TimeRange, triggerType: TriggerType, invokeFunction: () => void)

type TriggerType = `HitSound${SampleSet}${SampleSet}${Addition}${number | ''}`

enum SampleSet {
	None = '',
	All = 'All',
	Normal = 'Normal',
	Soft = 'Soft',
	Drum = 'Drum',
}

enum Addition {
	None = '',
	Whistle = 'Whistle',
	Finish = 'Finish',
	Clap = 'Clap',
}

Create a trigger group.

Trigger loops can be used to trigger animations based on play-time events. Although called loops, trigger loops only execute once when triggered.Trigger loops are zero-based similar to normal loops. If two overlap, the first will be halted and replaced by a new loop from the beginning. If they overlap any existing storyboarded events, they will not trigger until those transformations are no in effect.

makeTriggerType

function makeTriggerType(sampleSet: SampleSet, additionsSampleSet: SampleSet, addition: Addition, customSampleSet?: number): TriggerType

Helper to create TriggerType

Angle conversion

function degToRad(deg: number): number

Convert degrees to radians.

function radToDeg(rad: number): number

Convert radians to degrees.

Random

function randInt(min: number, max: number, seed?: number | string)

Random integer in the interval [min, max].

function randFloat(min: number, max: number, seed?: number | string)

Random float in the interval [min, max].

Note that the same seed will always return the same value. Leaving it empty will result in a true random value.

Vector math

function addVec(v1: Vector2, v2: Vector2): Vector2

Adds 2 vectors and returns a new vector.

function subVec(v1: Vector2, v2: Vector2): Vector2

Subtracts 2nd vector from 1st vector and returns a new vector.

function mulVec(v1: Vector2, v2: Vector2): Vector2

Multiplies 2 vectors and returns a new vector.

function mulVecScalar(v: Vector2, s: number): Vector2

Multiplies both x and y with a specified scalar and returns a new vector.

function addVecScalar(v: Vector2, s: number): Vector2

Adds a scalar to both x and y and returns a new vector.

function dotVec(v1: Vector2, v2: Vector2): number

Returns the dot product of 2 vectors.

function crossVec(v1: Vector2, v2: Vector2): number

Returns the cross product of 2 vectors.

function lengthSqrVec(v: Vector2): number

Returns the length squared of the vector.

function lengthVec(v: Vector2): number

Returns the length of the vector.

function areEqualVecs(v1: Vector2, v2: Vector2): boolean

Check if 2 vectors are equals.

function normalizeVec(v: Vector2): Vector2

Return a vector with the same direction but its length equals 1.

function cloneVec(v: Vector2): Vector2

Return a new Vector with the same x and y with the specified vector.

function interpolateVec(v1: Vector2, v2: Vector2, alpha: number): Vector2

Performs a linear interpolation between two vectors based on the given weighting, alpha = 0 will be v1 and alpha = 1 will be v2.

reportBuildTime

function reportBuildTime(sb: (end: () => void) => void)

Print to console how long it takes to generate the storyboard. Call end() once you have done everything.

interpolate

function interpolate(input: number, inputRange: [number, number], outputRange: [number, number], easing: Easing = Easing.Linear): number

Map a value from an input range to an output range. This will clamp the result if the input is outside of the input range.

hexToRgb

function hexToRgb(hex: string): Color

Convert hex color code to RGB color.

Default pallete

You can use the DefaultPallete constant to access the predefined colors if you are not sure which colors to use. All colors are picked up from here.

const DefaultPallete: { [key: string]: Color }

HideBg

function HideBg(path: string)

Hide the background image. This is a component so you need to call this after you have specified the storyboard context with useContext.

Dimensions

const WIDTH = 854
const HEIGHT = 480
const LEGACY_WIDTH = 640
const MIN_X = -107
const MAX_X = 747
const MIN_Y = 0
const MAX_Y = 480

A few constants you can use to get the storyboard dimensions quickly.

Official plugins