def test(self): t = SvPkg([SvSequence([SvObj(0, 1.0), SvObj(100, 2.0)]), SvSequence([SvObj(100, 3.0), SvObj(200, 4.0)]), SvSequence([SvObj(150, 5.0), SvObj(300, 6.0)])])\ .combine(combineMethod=SvPkg.CombineMethod.DROP_BY_POINT, combineMethodWindow=1) self.assertEqual(len(t), 5) t.appendInit([(0, 1.0), SvObj(100, 2.0), (1000, 2.0, True), 1000]) self.assertEqual(len(t), 9)
def f559(m: OsuMap): pkg = SvPkg([]) for off0, off1 in zip(offsets[:-1], offsets[1:]): diff = off1 - off0 pkg.append( SvSequence([(off0, diff * REF_BPM * 1.1), (off0 + 1, MIN_BPM), (off1, REF_BPM)])) m.bpms.extend( pkg.combine(SvPkg.CombineMethod.DROP_BY_POINT, combineMethodWindow=0, combinePriorityLast=False).writeAsBpm(OsuBpm))
def testCopy(self): # Test Copy seq = SvSequence([SvObj(0, 1.0), SvObj(100, 2.0)]) seqCopy = SvPkg.copyTo(seq=seq, offsets=[ 100, 200, 300 ]).combine(combineMethod=SvPkg.CombineMethod.DROP_BY_POINT) self.assertEqual(len(seqCopy), 4)
def testFit(self): # Test Fitting seq = SvSequence([SvObj(0, 0.5), SvObj(50, 1.5), SvObj(100, 1.0)]) seq = SvPkg.fit(seq, [0, 100, 200, 400, 600]).combine( SvPkg.CombineMethod.DROP_BY_POINT) self.assertEqual(len(seq), 9)
def test(self): t = SvPkg([SvSequence([SvObj(0, 1.0), SvObj(100, 2.0)]), SvSequence([SvObj(100, 3.0), SvObj(200, 4.0)]), SvSequence([SvObj(150, 5.0), SvObj(300, 6.0)])])\ .combine(combineMethod=SvPkg.CombineMethod.DROP_BY_POINT, combineMethodWindow=1) self.assertEqual(len(t), 5) t = SvPkg([SvSequence([SvObj(0, 1.0), SvObj(100, 2.0)]), SvSequence([SvObj(100, 3.0), SvObj(200, 4.0)]), SvSequence([SvObj(150, 5.0), SvObj(300, 6.0)])])\ .combine(combineMethod=SvPkg.CombineMethod.DROP_BY_BOUND, combineMethodWindow=1) self.assertEqual(len(t), 4) t = SvPkg([SvSequence([SvObj(0, 1.0), SvObj(100, 2.0)]), SvSequence([SvObj(100, 3.0), SvObj(200, 4.0)]), SvSequence([SvObj(150, 5.0), SvObj(300, 6.0)])])\ .combine(combineMethod=SvPkg.CombineMethod.IGNORE, combineMethodWindow=1) self.assertEqual(len(t), 6)
def svFuncSequencer(funcs: List[Union[float, Callable[[float], float], None]], offsets: Union[List[float], float, None] = None, repeats: int = 1, repeatGap: float = 0, startX: float = 0, endX: float = 1): """ Sets up a sequence using functions. :param funcs: Funcs to generate values. \ If List, values will be used directly. \ If Callable, values will be called with the X. \ If None, this will leave a gap in the sequence. :param offsets: Offsets to use on functions. \ If List, offsets will be used to map the funcs. \ If Float, all funcs are assumed to be separated by {float} ms. Starting from 0. \ If None, all funcs are assumed to be separated by 1 ms. Starting from 0. :param repeats: The amount of repeats. This affects the increment of the X argument passed to the Callables. \ If 0, only endX will be used. :param repeatGap: The gap between the repeats. :param startX: The starting X. :param endX: The ending X. """ length = len(funcs) if offsets is None: offsets = list(range(0, length)) # We use [:length] because sometimes arange will create too many for some reason (?) elif isinstance(offsets, (float, int)): offsets = list(arange(0, length * offsets, offsets))[:length] assert length == len(offsets) seq = SvSequence() for i, (offset, func) in enumerate(zip(offsets, funcs)): if isinstance(func, Callable): seq.appendInit([(offset, 0)]) elif isinstance(func, (float, int)): seq.appendInit([(offset, func)]) elif func is None: pass pkg = SvPkg.repeat(seq=seq, times=repeats, gap=repeatGap) nones = 0 for funcI, func in enumerate(funcs): if func is None: nones += 1 if isinstance(func, Callable): pkg.applyNth(func, funcI - nones, startX, endX) return pkg
def test2(self): # Test Mutual Cross seq1 = SvSequence( [SvObj(0, 0.5), SvObj(100, 2.0), SvObj(200, 3.0), SvObj(400, 2.0)]) seq2 = SvSequence([ SvObj(50, 0.1), SvObj(100, 10.0), SvObj(250, 5.0), SvObj(500, 0.2) ]) seq = SvPkg.crossMutualWith(seq1, seq2).combine( SvPkg.CombineMethod.DROP_BY_POINT) self.assertAlmostEqual(seq[0].multiplier, 0.5) self.assertAlmostEqual(seq[1].multiplier, 0.05) self.assertAlmostEqual(seq[2].multiplier, 20.0) self.assertAlmostEqual(seq[3].multiplier, 30.0) self.assertAlmostEqual(seq[4].multiplier, 15.0) self.assertAlmostEqual(seq[5].multiplier, 10.0) self.assertAlmostEqual(seq[6].multiplier, 0.4)
def svOsuMeasureLineA(firstOffset: float, lastOffset: float, funcs: List[Callable[[float], float]], referenceBpm: float, endBpm: float or None, paddingSize: int = 10, teleportBpm: float = 1e07, stopBpm: float = 1e-05, fillBpm: float or None = 1e-05, startX: float = 0, endX: float = 1) -> SvPkg: """ Generates Measure Line movement for osu! maps. Version 1. This is a beta function for svOsuMeasureLine, it may or may not work as expected. This handles multi functions a little bit better by stacking them in a single frame instead of flickering through them. Could be used for other VSRGs but if they support negative Scroll then it could be much easier. Sequence:: S_{_}...F{_F}..._S_T,S_{_}...F{_F}..._S_T,S_{_}...F{_F}..._S_T,... :param firstOffset: The first Offset to start the function (x = startX) :param lastOffset: The last Offset to end the function (x = endX) :param funcs: The functions to use. startX <= x <= endX will be called, expecting a BPM as an output. \ The more functions you have, the "laggier" it will be. :param referenceBpm: The bpm that is used to zero. Found by looking at BPM:XXX-XXX(Reference Bpm) in song select. :param paddingSize: The size of the padding, the larger the value, the lower the FPS :param teleportBpm: The bpm value for teleporting Bpms. :param stopBpm: The bpm value for stop Bpms. Cannot be 0. :param fillBpm: The bpm to use to fill such that the sequence ends on lastOffset. None for no fill. :param endBpm: The bpm to end the sequence with. :param startX: The starting X to use :param endX: The ending X to use """ # Optimized value to make sure that 1.0 in input means at the top of the screen. # Not accurate for all scrolls and different hit positions. SCALING_FACTOR = 9311250 / referenceBpm # Append a y = 0 to get diff on first func funcs = [lambda x: 0, *funcs] funcDiff = [] for funcI in range( len(funcs) - 1): # -1 due to the appended y = 0, -1 due to custom last func def f(x, i=funcI): sort = sorted([g(x) * SCALING_FACTOR for g in funcs]) for s in range(len(sort)): sort[s] = max(0.0, sort[s]) # We eliminate all negative inputs out = [g2 - g1 for g1, g2 in zip(sort[:-1], sort[1:])][i] if out == 0: return FALLBACK_ZERO_BPM else: return out funcDiff.append(deepcopy(f)) funcSeq = [] funcSeq.extend([stopBpm, *[None for _ in range(paddingSize)], funcDiff[0]]) for func in funcDiff[1:]: funcSeq.extend([None, func]) funcSeq.extend([None, stopBpm, None, teleportBpm]) msecPerFrame = len(funcSeq) duration = lastOffset - firstOffset frameCount = int(duration / msecPerFrame) pkg = svFuncSequencer(funcs=funcSeq, offsets=1, repeats=frameCount, repeatGap=1, startX=startX, endX=endX) pkg = SvPkg(map(lambda x: x.addOffset(firstOffset), pkg)) # Fill missing ending to fit to lastOffset if fillBpm is not None: seqLastOffset = firstOffset + frameCount * msecPerFrame pkg.append( SvSequence([ (offset, fillBpm) for offset in range(int(seqLastOffset), int(lastOffset)) ])) if endBpm is not None: pkg.append(SvSequence([(lastOffset, endBpm)])) return pkg