Example #1
0
    def __init__(self, other_members, tempo, bars, divs, inst_set):
        super(Session, self).__init__()
        self.tempo = tempo
        self.bars = bars
        self.divs = divs
        self.inst_set = inst_set
        spb = 60. / tempo
        beats = bars * 4
        self.seconds = spb * beats
        self.clock = Clock()
        self.temp_map = SimpleTempoMap(bpm=tempo)
        self.sched = Scheduler(self.clock, self.temp_map)
        # self.players = players
        self.IM = InstrumentManager(self.sched)
        self.add(self.IM)

        ### NEW CODE ###
        self.pattern_list = PatternList(self.bars, self.tempo, self.inst_set)
        self.add(self.pattern_list)

        track = Track(num_lanes, self.bars, self.tempo)
        track.position.y = Window.height * 0.025
        controller = InstrumentKeyboard(default_keycodes, lock_in_keycode)
        self.player = Player(controller, track, inst_set)
        self.player.position.x = Window.width - player_size[0] - 20
        self.add(self.player)
        self.vplayers = []
        self.add_band_members(other_members)
        self.IM.add(self.player.instrument)

        self.clock.offset = self.seconds - spb

        self.paused = True
        self.start()
Example #2
0
 def __init__(self, callback=None):
     super(DataPlayer, self).__init__()
     self.sched = Scheduler(Clock(),
                            tempo_map=[(0, 0),
                                       (100000000000,
                                        100000000000 / 60 * 480)])
     self.beat_len = kTicksPerQuarter
     self.on_cmd = None
     self.off_cmd = None
     self.playing = False
     self.callback = None
     self.barData = None
Example #3
0
    def __init__(self, norm, pos, tempo, clock, tempo_map, touch_points,
                 block_handler):
        super(TempoCursor, self).__init__()
        self.norm = norm
        self.pos = pos
        self.size = self.norm.nt((70, 70))

        self.cursor = CEllipse(cpos=pos, csize=self.size)
        self.add(Color(1, 1, 1))
        self.add(self.cursor)

        self.tempo = tempo
        self.clock = clock
        self.tempo_map = tempo_map
        self.sched = Scheduler(self.clock, self.tempo_map)

        self.block_handler = block_handler

        # 0..15, for 16th note granularity
        self.touch_points = touch_points
        self.index = 0

        # add touch markers
        self.add(PushMatrix())
        self.add(Translate(*pos))
        for touch_point in self.touch_points:
            self.add(Rotate(angle=-360 * touch_point / 16))
            self.add(Color(159 / 255, 187 / 255, 208 / 255))  # blue
            self.add(Line(points=(0, 0, 0, self.norm.nv(25)), width=2))
            self.add(Rotate(angle=360 * touch_point / 16))
        self.add(PopMatrix())

        # add current time marker
        self.add(PushMatrix())
        self.add(Translate(*pos))
        self.time_marker = Line(points=(0, 0, 0, self.norm.nv(30)), width=3)
        self.rotate = Rotate(angle=0)
        self.add(self.rotate)
        self.add(Color(0, 0, 0))
        self.add(self.time_marker)
        self.add(PopMatrix())

        self.on_update(0)

        cur_tick = self.sched.get_tick()
        next_tick = quantize_tick_up(cur_tick, kTicksPerQuarter * 4)
        next_tick += self.calculate_tick_interval(0, self.touch_points[0])
        self.sched.post_at_tick(self.touch_down, next_tick)
Example #4
0
    def __init__(self):
        super(MainWidget3, self).__init__()

        # create a clock and TempoMap
        self.clock = Clock()
        self.tempo = SimpleTempoMap(120)

        # create a Scheduler
        self.sched = Scheduler(self.clock, self.tempo)

        # and text to display our status
        self.label = topleft_label()
        self.add_widget(self.label)

        # to see accumulated output:
        self.output_text = ''
Example #5
0
class MainWidget4(BaseWidget):
    def __init__(self):
        super(MainWidget4, self).__init__()

        self.audio = Audio(2)
        self.synth = Synth('../data/FluidR3_GM.sf2')
        self.audio.set_generator(self.synth)

        # create clock, tempo_map, scheduler
        self.clock = Clock()
        self.tempo_map = SimpleTempoMap(120)
        self.sched = Scheduler(self.clock, self.tempo_map)

        # create the metronome:
        self.metro = Metronome(self.sched, self.synth)

        # and text to display our status
        self.label = topleft_label()
        self.add_widget(self.label)

    def on_key_down(self, keycode, modifiers):
        if keycode[1] == 'c':
            self.clock.toggle()

        if keycode[1] == 'm':
            self.metro.toggle()

        bpm_adj = lookup(keycode[1], ('up', 'down'), (10, -10))
        if bpm_adj:
            new_tempo = self.tempo_map.get_tempo() + bpm_adj
            self.tempo_map.set_tempo(new_tempo, self.sched.get_time())

    def on_update(self):
        # scheduler and audio get poked every frame
        self.sched.on_update()
        self.audio.on_update()

        bpm = self.tempo_map.get_tempo()

        self.label.text = self.sched.now_str() + '\n'
        self.label.text += 'Metronome:' + ("ON" if self.metro.playing else
                                           "OFF") + '\n'
        self.label.text += 'tempo:{}\n'.format(bpm)
        self.label.text += 'm: toggle Metronome\n'
        self.label.text += 'up/down: change speed\n'
Example #6
0
    def __init__(self):
        super(MainWidget4, self).__init__()

        self.audio = Audio(2)
        self.synth = Synth('../data/FluidR3_GM.sf2')
        self.audio.set_generator(self.synth)

        # create clock, tempo_map, scheduler
        self.clock = Clock()
        self.tempo_map = SimpleTempoMap(120)
        self.sched = Scheduler(self.clock, self.tempo_map)

        # create the metronome:
        self.metro = Metronome(self.sched, self.synth)

        # and text to display our status
        self.label = topleft_label()
        self.add_widget(self.label)
Example #7
0
 def __init__(self, songPattern, key, clock):
     self.slack_timout = beats_to_time(.5, self.bpm)
     self.gems, self.bars = self._initialize_bars(songPattern, key)
     self.synth = Synth('../bank/FluidR3_GM.sf2')
     totalBeats = self.measuresPerCall * self.numRepeats * len(
         self.bars) * 4
     totalTime = beats_to_time(totalBeats, self.bpm)
     totalOffsetTicks = self.endBarTicks * totalBeats / 4
     totalTicks = totalBeats / 4 * self.barLenTicks
     data = [(0, 0),
             ((totalTime + ticks_to_time(totalOffsetTicks, self.bpm)) * 2,
              totalTicks * 2)]
     self.tempo = TempoMap(data=data)
     self.num_bars = len(data)
     self.scheduler = Scheduler(clock, self.tempo)
     self.increment_bar = None
     self.catch_passes = None
     self.on_commands = []  # commands for a bar of call and response
     self.off_commands = []
     self.active_gems = []
     self.gem_commands = []
     self.bar_tick = 0
     self.bar_index = 0
     print(songPattern)
Example #8
0
class MainWidget3(BaseWidget):
    def __init__(self):
        super(MainWidget3, self).__init__()

        # create a clock and TempoMap
        self.clock = Clock()
        self.tempo = SimpleTempoMap(120)

        # create a Scheduler
        self.sched = Scheduler(self.clock, self.tempo)

        # and text to display our status
        self.label = topleft_label()
        self.add_widget(self.label)

        # to see accumulated output:
        self.output_text = ''

    def on_key_down(self, keycode, modifiers):
        if keycode[1] == 'c':
            self.clock.toggle()

        if keycode[1] == 'a':
            now = self.sched.get_tick()
            later = now + (2 * kTicksPerQuarter)
            self.output_text += "now={}. post at tick={}\n".format(now, later)
            self.cmd = self.sched.post_at_tick(self._do_it, later, 'hello')

        if keycode[1] == 'b':
            self.sched.remove(self.cmd)

    def _do_it(self, tick, msg):
        self.output_text += "{} (at {})\n".format(msg, tick)

    def on_update(self):
        # scheduler gets called every frame to process events
        self.sched.on_update()
        self.label.text = self.sched.now_str() + '\n'
        self.label.text += 'c: toggle clock\n'
        self.label.text += 'a: post event\n'
        self.label.text += 'b: remove event\n'
        self.label.text += self.output_text
Example #9
0
class DataPlayer(object):
    """Plays a steady click every beat.
    """
    def __init__(self, callback=None):
        super(DataPlayer, self).__init__()
        self.sched = Scheduler(Clock(),
                               tempo_map=[(0, 0),
                                          (100000000000,
                                           100000000000 / 60 * 480)])
        self.beat_len = kTicksPerQuarter
        self.on_cmd = None
        self.off_cmd = None
        self.playing = False
        self.callback = None
        self.barData = None

    def start(self):
        if self.playing:
            return
        self.playing = True
        now = self.sched.get_tick()
        next_beat = quantize_tick_up(now, self.beat_len)
        self.on_cmd = self.sched.post_at_tick(self.tick, next_beat)

    def stop(self):
        if not self.playing:
            return
        self.playing = False
        if self.off_cmd:
            self.off_cmd.execute()
        self.sched.remove(self.on_cmd)
        self.sched.remove(self.off_cmd)
        self.on_cmd = None
        self.off_cmd = None

    # def set_bar(self, bar_data):
    #     self.barData = bar_data

    def toggle(self):
        if self.playing:
            self.stop()
        else:
            self.start()

    def tick(self, tick, ignore):
        next_beat = tick + self.beat_len
        self.on_cmd = self.sched.post_at_tick(self.tick, next_beat)


# class ScoreScreen(BaseWidget):
#     def __init__(self, scoreCard=ScoreCard(), callBack=None):
#         super(ScoreScreen, self).__init__()
#         w = Window.width//3
#         buttonSize = (w, w//3)
#         buttonAnchor = (3*Window.width//4, Window.height//4)
#         self.scoreCard = scoreCard
#         titleString= "Score Card"
#         title = Rectangle(pos=(Window.width//4, 3*Window.height//4), size=(2*Window.width//4, Window.height//5))
#         label = CoreLabel(text=titleString, font_size=56)
#         label.refresh()
#         text = label.texture
#         title.texture = text
#         self.objects = AnimGroup()
#         self.cards = self.generateTiles(self.scoreCard.getAllStrings())
#         self.canvas.add(Rectangle(size=(Window.width,Window.height)))
#         self.canvas.add(self.objects)
#         self.canvas.add(Color(*(.05,.28,.1)))
#         self.canvas.add(title)
#         [self.objects.add(card) for card in self.cards]
#         print('ScoreScreen Initialized')

#     def generateTiles(self, scoreStrings):
#         print(scoreStrings)
#         xa,ya = (10,0)
#         x = xa
#         y = ya
#         padding = 10
#         tile = Card(scoreStrings[0])

#         w,h = tile.getSize()
#         outputs = [tile]
#         for i in range(1, len(scoreStrings)):
#             if i % 5 == 0:
#                 y += h + padding
#                 x = xa - w- padding
#             x += w + padding
#             outputs.append(Card(scoreStrings[i], pos=(x,y), size=(w,h)))
#         return outputs

#     def on_update(self):
#         self.objects.on_update()
#         # print('homescreenUpdate')

#     def on_key_down(self, keycode, modifiers):
#         pass

# run(ScoreScreen)
Example #10
0
class TempoCursor(InstructionGroup):
    """
    This module is a timed cursor that creates touch_down events on any objects that it is placed
    over. It uses an underlying tempo that is room-wide.
    """
    name = 'TempoCursor'

    def __init__(self, norm, pos, tempo, clock, tempo_map, touch_points,
                 block_handler):
        super(TempoCursor, self).__init__()
        self.norm = norm
        self.pos = pos
        self.size = self.norm.nt((70, 70))

        self.cursor = CEllipse(cpos=pos, csize=self.size)
        self.add(Color(1, 1, 1))
        self.add(self.cursor)

        self.tempo = tempo
        self.clock = clock
        self.tempo_map = tempo_map
        self.sched = Scheduler(self.clock, self.tempo_map)

        self.block_handler = block_handler

        # 0..15, for 16th note granularity
        self.touch_points = touch_points
        self.index = 0

        # add touch markers
        self.add(PushMatrix())
        self.add(Translate(*pos))
        for touch_point in self.touch_points:
            self.add(Rotate(angle=-360 * touch_point / 16))
            self.add(Color(159 / 255, 187 / 255, 208 / 255))  # blue
            self.add(Line(points=(0, 0, 0, self.norm.nv(25)), width=2))
            self.add(Rotate(angle=360 * touch_point / 16))
        self.add(PopMatrix())

        # add current time marker
        self.add(PushMatrix())
        self.add(Translate(*pos))
        self.time_marker = Line(points=(0, 0, 0, self.norm.nv(30)), width=3)
        self.rotate = Rotate(angle=0)
        self.add(self.rotate)
        self.add(Color(0, 0, 0))
        self.add(self.time_marker)
        self.add(PopMatrix())

        self.on_update(0)

        cur_tick = self.sched.get_tick()
        next_tick = quantize_tick_up(cur_tick, kTicksPerQuarter * 4)
        next_tick += self.calculate_tick_interval(0, self.touch_points[0])
        self.sched.post_at_tick(self.touch_down, next_tick)

    def on_update(self, dt):
        self.sched.on_update()

        cur_time = self.sched.get_time()
        cur_tick = self.sched.get_tick()
        angle = (360 * (cur_tick / (kTicksPerQuarter * 4))) % 360
        self.rotate.angle = -angle

    def calculate_tick_interval(self, p1, p2):
        if p2 > p1:
            return (p2 - p1) / 4 * kTicksPerQuarter
        elif p1 > p2:
            return (p2 + 16 - p1) / 4 * kTicksPerQuarter
        else:
            return 0

    def round_to_sixteenth(self, tick):
        kTicksPerSixteenth = kTicksPerQuarter / 4
        kTicksPerMeasure = kTicksPerQuarter * 4
        measure = math.floor(tick / kTicksPerMeasure)
        beat = round(16 * ((tick % kTicksPerMeasure) / kTicksPerMeasure))
        return measure * kTicksPerMeasure + (beat * kTicksPerSixteenth)

    def touch_down(self, tick):
        self._touch_down()

        cur_tick = self.round_to_sixteenth(self.sched.get_tick())
        next_index = (self.index + 1) % len(self.touch_points)
        p1 = self.touch_points[self.index]
        p2 = self.touch_points[next_index]
        interval = kTicksPerQuarter * 4 if len(self.touch_points) == 1 \
              else self.calculate_tick_interval(p1, p2)
        next_tick = cur_tick + interval

        self.index = next_index

        self.sched.post_at_tick(self.touch_down, next_tick)

    def _touch_down(self):
        for block in self.block_handler.blocks.objects:
            if in_bounds(self.pos, block.pos, block.size):
                block.flash()
Example #11
0
class Ticker(object):
    ''' 
    Maps beat patterns to ticks (time stamps) and plays the call of the call and response (if enabled)
    This class assumes quarter notes as beats.
    becase every loop in this game is 4 measures, we will assume a loop to last 16 beats.  (4/4)
    '''
    bpm = 400
    numRepeats = 3
    measuresPerCall = 1
    channel = 0
    vel = 80
    playQueues = 1
    noteDuration = 1
    nowBarHeight = Window.height // 2
    endBarTicks = kTicksPerQuarter * .5
    barLenTicks = kTicksPerQuarter * 4 + endBarTicks
    gemRadius = 100
    padding = gemRadius + 50

    def __init__(self, songPattern, key, clock):
        self.slack_timout = beats_to_time(.5, self.bpm)
        self.gems, self.bars = self._initialize_bars(songPattern, key)
        self.synth = Synth('../bank/FluidR3_GM.sf2')
        totalBeats = self.measuresPerCall * self.numRepeats * len(
            self.bars) * 4
        totalTime = beats_to_time(totalBeats, self.bpm)
        totalOffsetTicks = self.endBarTicks * totalBeats / 4
        totalTicks = totalBeats / 4 * self.barLenTicks
        data = [(0, 0),
                ((totalTime + ticks_to_time(totalOffsetTicks, self.bpm)) * 2,
                 totalTicks * 2)]
        self.tempo = TempoMap(data=data)
        self.num_bars = len(data)
        self.scheduler = Scheduler(clock, self.tempo)
        self.increment_bar = None
        self.catch_passes = None
        self.on_commands = []  # commands for a bar of call and response
        self.off_commands = []
        self.active_gems = []
        self.gem_commands = []
        self.bar_tick = 0
        self.bar_index = 0
        print(songPattern)

    def reset(self):
        #TODO
        for i in range(len(self.num_bars)):
            self.clear_bar(i)
        self.bar_index = 0

    def create_bar(self, barIndex):
        self.bar_tick = quantize_tick_up(self.scheduler.get_tick())
        self.active_gems = self.gems[barIndex]
        # print(list(map(lambda x: x.beat, self.active_gems)))
        # [gem.activate() for gem in self.active_gems]
        self._initializeBarAudio(barIndex)
        self._initializeBarGems(barIndex)
        # self.increment_bar

    def clear_bar(self, barIndex):
        self._clearBarAudio(barIndex)
        self._clearBarGems()

    def getRelativeTick(self):
        tick = quantize_tick_up(self.scheduler.get_tick())
        tick = (tick - self.bar_tick) % self.barLenTicks
        return tick

    def getTick(self):
        return quantize_tick_up(self.scheduler.get_tick())

    def getTargetGem(self):
        tick = self.getRelativeTick()
        beatApprox = (tick / self.barLenTicks) * 4
        #find gem by tick
        targetGem = None
        minDist = 9999999999
        for gem in self.active_gems:
            if gem.hit or gem.miss:
                # print("skipping hit gem (target) ", tick)
                continue
            gemDist = min(abs(gem.beat - beatApprox),
                          abs(beatApprox - gem.beat))
            if gemDist < minDist:
                minDist = gemDist
                targetGem = gem
        # print("minimum dist: ", minDist)
        # print("targetGem: ", gem.beat)
        # print("beat approx: ", beatApprox)
        return targetGem

    def on_update(self):
        self.scheduler.on_update()
        tick = self.getTick()
        ticksEllapsed = (tick - self.bar_tick) % (self.barLenTicks * 4)
        if ticksEllapsed <= self.barLenTicks:
            return "call"
        elif ticksEllapsed <= self.numRepeats * self.barLenTicks:
            return "response"
        else:
            # print("tick %f, ticksEll %f, barTick %f, barLen %f " % (tick, ticksEllapsed, self.bar_tick, self.barLenTicks))
            print('nextStatus')
            return "next"

    def bars_remaining(self):
        tick = self.getTick()
        ticksEllapsed = (tick - self.bar_tick) % (self.barLenTicks * 4)
        barNum = round((self.numRepeats * self.barLenTicks - ticksEllapsed) /
                       self.barLenTicks)
        barsRemaining = np.clip(self.numRepeats - barNum - 1, 0,
                                self.numRepeats - 1)
        return barsRemaining

    def _initialize_bars(self, pattern, key):
        gem_bars = []
        chord_bars = []

        for bar in pattern:
            numGems = len(bar)
            assert numGems > 1
            w = (Window.width) // 5
            x = self.padding
            y = self.nowBarHeight
            chords_and_ticks = []
            gems = []
            for b in bar:
                assert b[1] <= self.measuresPerCall * 4 and b[1] > 0
                chord = key.generateChord(b[0])
                chords_and_ticks.append((chord, b[1]))
                x = w * b[1]
                gem = Gem(chord, (x, y), self.gemRadius, self.slack_timout,
                          b[1])
                gems.append(gem)

            chord_bars.append(chords_and_ticks)
            gem_bars.append(gems)
        return gem_bars, chord_bars

    def _initializeBarAudio(self, barIndex):
        bar_tick = int(self.bar_tick)
        bar = self.bars[barIndex]
        # print(self.bar_tick)
        assert self.numRepeats >= self.playQueues
        for i in range(self.playQueues):
            for chord, beat in bar:
                tick = bar_tick + beat * kTicksPerQuarter
                # print('audio ', tick)
                self.on_commands.append(
                    self.scheduler.post_at_tick(self._playChord, tick, chord))
                self.off_commands.append(
                    self.scheduler.post_at_tick(
                        self._endChord,
                        tick + kTicksPerQuarter * self.noteDuration, chord))
            bar_tick += self.barLenTicks

    def _clearBarAudio(self, barIndex):
        for c in self.on_commands:
            self.scheduler.remove(c)
        for c in self.off_commands:
            self.scheduler.remove(c)
            c.execute()

    def _initializeBarGems(self, barIndex):
        slackWinOffset = quantize_tick_up(
            float(self.slack_timout) / 2 * kTicksPerQuarter)
        bar_tick = int(self.bar_tick)
        bar = self.gems[barIndex]
        for i in range(self.numRepeats):
            for gem in bar:
                tick = bar_tick + gem.beat * kTicksPerQuarter
                if i > 0:
                    self.gem_commands.append(
                        self.scheduler.post_at_tick(self._startGemTimer,
                                                    tick - slackWinOffset,
                                                    gem))
                    ticks_to_time(tick - slackWinOffset, self.bpm)
            bar_tick += self.barLenTicks
            self.gem_commands.append(
                self.scheduler.post_at_tick(self._onCompleteMeasure, bar_tick))

    def _refreshBarGems(self):
        for gem in self.active_gems:
            gem.on_reset()
            # gem.activate()

    def _clearBarGems(self):
        for gem in self.active_gems:
            # pass
            gem.exit()

    def _onCompleteMeasure(self, tick, temp=None):
        allHit = True
        for gem in self.active_gems:
            allHit = allHit and gem.hit
        if allHit and not self.on_update() == "call":
            self.increment_bar(perfect=allHit)
            print('increment bar')
        else:
            print("measure over, resetting gems - %f" % self.getRelativeTick())
            self.catch_passes(True)
            self._refreshBarGems()

    def _startGemTimer(self, tick, gem):
        ''' starts the gem timer'''
        # print("start gem timer %f " % self.getRelativeTick())
        if not gem.hit:
            gem.activate()

    def _playChord(self, tick, chord):
        for note in chord._getMidiTones():
            self.synth.noteon(self.channel, note, self.vel)

    def _endChord(self, tick, chord):
        for note in chord._getMidiTones():
            self.synth.noteoff(self.channel, note)

    def initialize_callbacks(self, increment, catch_passes):
        self.increment_bar = increment
        self.catch_passes = catch_passes
Example #12
0
class Session(GameObject):
    def __init__(self, other_members, tempo, bars, divs, inst_set):
        super(Session, self).__init__()
        self.tempo = tempo
        self.bars = bars
        self.divs = divs
        self.inst_set = inst_set
        spb = 60. / tempo
        beats = bars * 4
        self.seconds = spb * beats
        self.clock = Clock()
        self.temp_map = SimpleTempoMap(bpm=tempo)
        self.sched = Scheduler(self.clock, self.temp_map)
        # self.players = players
        self.IM = InstrumentManager(self.sched)
        self.add(self.IM)

        ### NEW CODE ###
        self.pattern_list = PatternList(self.bars, self.tempo, self.inst_set)
        self.add(self.pattern_list)

        track = Track(num_lanes, self.bars, self.tempo)
        track.position.y = Window.height * 0.025
        controller = InstrumentKeyboard(default_keycodes, lock_in_keycode)
        self.player = Player(controller, track, inst_set)
        self.player.position.x = Window.width - player_size[0] - 20
        self.add(self.player)
        self.vplayers = []
        self.add_band_members(other_members)
        self.IM.add(self.player.instrument)

        self.clock.offset = self.seconds - spb

        self.paused = True
        self.start()

    def add_band_members(self, other_members):
        for other_member in other_members:
            vcontroller = InstrumentController(16, other_member['id'])
            vtrack = VirtualTrack(num_lanes, self.bars, self.tempo)
            vplayer = VirtualPlayer(vcontroller, vtrack)
            self.vplayers.append(vplayer)
            self.add(vplayer)

    def on_key_down(self, event):
        # if event.keycode[1] == 'enter':on
        # 	self.toggle()
        pass

    def toggle(self):
        if self.paused:
            self.paused = False
            self.start()
        else:
            self.paused = True
            self.stop()

    def stop(self):
        self.clock.stop()

    def start(self):
        self.clock.start()

    def next_player(self, sequence):
        if self.current_player < self.num_players:
            self.players[self.current_player].note_sequence = sequence
            self.players[self.current_player].stop_composing()
            self.current_player += 1
        if self.current_player < self.num_players:
            self.players[self.current_player].start_composing()

    def on_lock_in(self, event):
        self.next_player(event.action['sequence'])

    def on_update(self):
        self.sched.on_update()
        # for player in self.players:
        now = self.clock.get_time() % self.seconds
        for vplayer in self.vplayers:
            vplayer.set_now(now)
        self.player.set_now(now)
        self.pattern_list.set_now(self.clock.get_time() % self.seconds)