Exemplo n.º 1
0
 def devices():
     """ return dict with names and ids of sound devices for playback (aka speakers) """
     result=dict()
     if HAS_SOUNDCARD:
       for m in soundcard.all_speakers():
         result[m.name]=m.id
     return result
Exemplo n.º 2
0
    async def async_step_user(self, user_input=None):
        """Handle the initial step."""
        errors = {}
        if user_input is not None:
            try:
                info = await validate_input(self.hass, user_input)

                return self.async_create_entry(title=info["title"],
                                               data=user_input)
            except InvalidSinkID:
                errors["base"] = "invalid_sink_id"
            except Exception:  # pylint: disable=broad-except
                _LOGGER.exception("Unexpected exception")
                errors["base"] = "unknown"

        sinks = {sink.id: sink.name + ':' + sink.id for sink in all_speakers()}
        DATA_SCHEMA = vol.Schema({
            vol.Required(CONF_NAME, default="PulseAudio Speaker"):
            str,
            vol.Required(CONF_SINK, default=default_speaker().id):
            vol.In(sinks)
        })
        return self.async_show_form(step_id="user",
                                    data_schema=DATA_SCHEMA,
                                    errors=errors)
Exemplo n.º 3
0
 def scard_query_devices(self) -> List[Dict[str, Any]]:
     speakers = soundcard.all_speakers()
     result = []
     for speaker in speakers:
         info = speaker._get_info()
         info['id'] = speaker.id
         result.append(info)
     return result
Exemplo n.º 4
0
 def get_speakers() -> List[str]:
     """
     :return: All speaker names, plus DEFAULT_SOUND_NAME.
     """
     l = [DEFAULT_SOUND_NAME]
     for m in sc.all_speakers():
         l.append(m.name)
     return l
Exemplo n.º 5
0
 def scard_query_devices(self) -> List[Dict[int, Dict[str, Any]]]:
     speakers = soundcard.all_speakers()
     result = []
     for idx, speaker in enumerate(speakers):
         info = speaker._get_info()
         info['id'] = speaker.id
         result.append({idx: info})
     return result
Exemplo n.º 6
0
def start_mixer():

    all_speakers = sc.all_speakers()
    (speaker1, ) = filter(speaker_name_matcher, all_speakers)

    output1 = speaker1.player(samplerate=SAMPLERATE, blocksize=BLOCKSIZE)
    output1.__enter__()

    all_inputs = sc.all_microphones()
    (mic1, mic2) = filter(mic_name_matcher, all_inputs)

    input1 = mic1.recorder(samplerate=SAMPLERATE, blocksize=BLOCKSIZE)
    input1.__enter__()
    input2 = mic1.recorder(samplerate=SAMPLERATE, blocksize=BLOCKSIZE)
    input2.__enter__()

    all_inputs = sc.all_microphones(include_loopback=True)
    (loopback, ) = filter(loopback_name_matcher, all_inputs)

    opendsh = loopback.recorder(samplerate=SAMPLERATE, blocksize=BLOCKSIZE)
    opendsh.__enter__()

    player = None
    playing_count = 0

    while True:

        playing = None

        i1 = input1.record(numframes=NUMFRAMES)
        i2 = input2.record(numframes=NUMFRAMES)
        od = opendsh.record(numframes=NUMFRAMES)

        if player == Inputs.CHANNEL1 and playing_count < MIN_PLAYING_COUNT:
            output1.play(i1)
            playing = Inputs.CHANNEL1
        elif player == Inputs.CHANNEL2 and playing_count < MIN_PLAYING_COUNT:
            output1.play(i2)
            playing = Inputs.CHANNEL2

        elif not is_silent(i1, "channel1"):
            output1.play(i1)
            playing = Inputs.CHANNEL1
        elif not is_silent(i2, "channel2"):
            output1.play(i2)
            playing = Inputs.CHANNEL2

        else:
            output1.play(od)
            playing = Inputs.OPENDSH

        if playing != player:
            logger.debug("switching to source: {} after {} iterations".format(
                playing, playing_count))
            player = playing
            playing_count = 0
        else:
            playing_count += 1
Exemplo n.º 7
0
def pick_device():
    devices = sc.all_speakers()
    for x in range(0, len(devices)):
        print("{}: {} ({})".format(x, devices[x].name, devices[x].id))
    inp = int(input("Enter the device number: "))
    if inp > 0 and inp < len(devices):
        return devices[inp]
    else:
        print("Invalid number ", inp)
        return pick_device()
Exemplo n.º 8
0
    def __init__(self):

        # get a list of all speakers:
        speakers = sc.all_speakers()
        # get the current default speaker on your system:
        self.default_speaker = sc.default_speaker()
        # get a list of all microphones:
        mics = sc.all_microphones()
        # get the current default microphone on your system:
        self.default_mic = self.__get_build_in_mic(mics)
Exemplo n.º 9
0
async def validate_input(hass: core.HomeAssistant, data):
    """Validate the user input allows us to connect.

    Data has the keys from DATA_SCHEMA with values provided by the user.
    """

    sink_ids = [sink.id for sink in all_speakers()]

    if data[CONF_SINK] not in sink_ids:
        raise InvalidSinkID

    # Return info that you want to store in the config entry.
    return {"title": "PulseAudio: " + data[CONF_SINK]}
Exemplo n.º 10
0
def print_all_devices() -> None:
    print("speakers")
    spk_all = sc.all_speakers()
    spk_default = sc.default_speaker()
    for spk in spk_all:
        prefix = "*" if str(spk) == str(spk_default) else " "
        print(f"{prefix} {spk.name}       id: {spk.id}")

    print("microphones")
    mic_all = sc.all_microphones()
    mic_default = sc.default_microphone()
    for mic in mic_all:
        prefix = "*" if str(mic) == str(mic_default) else " "
        print(f"{prefix} {mic.name}       id: {mic.id}")
Exemplo n.º 11
0
 def scard_query_device_details(self, device: Optional[Union[int, str]] = None, kind: Optional[str] = None) -> Any:
     speakers = soundcard.all_speakers()
     if type(device) == str:
         for speaker in speakers:
             if speaker.id == device:
                 result = speaker._get_info()
                 result['id'] = speaker.id
                 return result
     else:
         for idx, speaker in enumerate(speakers):
             if idx == device:
                 result = speaker._get_info()
                 result['id'] = speaker.id
                 return result
     raise LookupError("no such device")
Exemplo n.º 12
0
def play():
    all_speakers = sc.all_speakers()
    output = all_speakers[1]

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('', 3001))
    sock.listen(1)
    conn, addr = sock.accept()

    player = output.player(44100)
    player.__enter__()
    while True:
        data = conn.recv(1024)
        arr = np.frombuffer(data, dtype=np.float32)
        player.play(arr)
    conn.close()
Exemplo n.º 13
0
    def test(self):
        self.sound_cards = soundcard.all_speakers()
        self.mics = soundcard.all_microphones()

        if len(self.sound_cards) > 0:
            self.sound_card_present = True
            self.default_sound_card = str(soundcard.default_speaker()).replace(
                "<", "").replace(">", "")
        else:
            self.sound_card_present = False
            self.default_sound_card = "No default sound card found. May not be enabled or plugged in."

        if len(self.mics) > 0:
            self.mic_present = True
            self.default_mic = str(soundcard.default_microphone()).replace(
                "<", "").replace(">", "")
        else:
            self.mic_present = False
            self.default_mic = "No default mic found. May not be enabled or plugged in."
        return self
Exemplo n.º 14
0
def run():
    print("connecting to socket server")
    # client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # client.connect((TCP_IP_ADDR, TCP_PORT_NO))
    print("getting all microphones")
    # all_microphones = sc.all_microphones(include_loopback=True)
    # print("got all microphones: {}".format(all_microphones))
    # loopback = next((microphone for microphone in all_microphones if microphone.isloopback and microphone.name.find('Speakers') != -1), None)
    loopback = sc.default_microphone()
    if loopback is None:
        print("No loopback device detected, exiting...")
        exit(1)
    print("Found a loopback microphone with: {}".format(loopback))
    try:
        with loopback.recorder(
                samplerate=44100,
                channels=2) as mic:  #, wave.open('test.wav', 'wb') as wav:
            # wav.setparams((1, 2, 44100, 0, 'NONE', 'NONE'))
            spk = next(speaker for speaker in sc.all_speakers()
                       if speaker.name.find('ODAC') != -1)
            print(spk)
            # mic.__enter__()
            # while True:
            while True:
                try:
                    data = loopback.record(1024, 44100)
                    spk.play(data, 44100)
                    # byte = data.tobytes()
                    # wav.writeframes(byte)
                    # mic.flush()
                    # byte = bytes('testing', 'utf-8')
                    # client.send(byte)
                    # sys.stdout.flush()
                    # client.sendto(byte, (UDP_IP_ADDR, UDP_PORT_NO))
                    # mic.flush()
                except Exception as e:
                    print("Uh oh... failed: {}".format(e))
                    exit(1)
    except KeyboardInterrupt:
        # client.close()
        wav.close()
Exemplo n.º 15
0
def all_outputs():
    return sc.all_speakers()
Exemplo n.º 16
0
def get_interface(cfo):
    _test0 = soundcard.all_speakers()
    _test1 = soundcard.all_microphones()
    sp = soundcard.get_speaker(cfo.OUT_CARD_NAME)
    mc = soundcard.get_microphone(cfo.IN_CARD_NAME)
    return sp, mc
Exemplo n.º 17
0
def main():
    # extend the OptionParser so that we can print multiple paragraphs in
    # the help text
    class MyParser(OptionParser):
        def format_description(self, formatter):
            result = []
            for paragraph in self.description:
                result.append(formatter.format_description(paragraph))
            return "\n".join(result[:-1])  # drop last \n

        def format_epilog(self, formatter):
            result = []
            for paragraph in self.epilog:
                result.append(formatter.format_epilog(paragraph))
            return "".join(result)

    usage = "%prog -s SERVER -p PORT -f FREQ -m MODE [other options]"
    description = [
        "kiwiclientd.py receives audio from a KiwiSDR and plays"
        " it to a (virtual) sound device. This can be used to"
        " send KiwiSDR audio to various programs to decode the"
        " received signals."
        " This program also accepts hamlib rigctl commands over"
        " a network socket to change the kiwisdr frequency"
        " To stream multiple KiwiSDR channels at once, use the"
        " same syntax, but pass a list of values (where applicable)"
        " instead of single values. For example, to stream"
        " two KiwiSDR channels in USB to the virtual sound cards"
        " kiwisdr0 & kiwisdr1, with the rigctl ports 6400 &"
        " 6401 respectively, run the following:",
        "$ kiwiclientd.py -s kiwisdr.example.com -p 8073 -f 10000 -m usb --snddev kiwisnd0,kiwisnd1 --rigctl-port 6400,6401",
        ""
    ]
    epilog = []  # text here would go after the options list
    parser = MyParser(usage=usage, description=description, epilog=epilog)
    parser.add_option('-s',
                      '--server-host',
                      dest='server_host',
                      type='string',
                      default='localhost',
                      help='Server host (can be a comma-separated list)',
                      action='callback',
                      callback_args=(str, ),
                      callback=get_comma_separated_args)
    parser.add_option(
        '-p',
        '--server-port',
        dest='server_port',
        type='string',
        default=8073,
        help='Server port, default 8073 (can be a comma-separated list)',
        action='callback',
        callback_args=(int, ),
        callback=get_comma_separated_args)
    parser.add_option(
        '--pw',
        '--password',
        dest='password',
        type='string',
        default='',
        help='Kiwi login password (if required, can be a comma-separated list)',
        action='callback',
        callback_args=(str, ),
        callback=get_comma_separated_args)
    parser.add_option(
        '--tlimit-pw',
        '--tlimit-password',
        dest='tlimit_password',
        type='string',
        default='',
        help=
        'Connect time limit exemption password (if required, can be a comma-separated list)',
        action='callback',
        callback_args=(str, ),
        callback=get_comma_separated_args)
    parser.add_option(
        '-u',
        '--user',
        dest='user',
        type='string',
        default='kiwiclientd',
        help='Kiwi connection user name (can be a comma-separated list)',
        action='callback',
        callback_args=(str, ),
        callback=get_comma_separated_args)
    parser.add_option(
        '--log',
        '--log-level',
        '--log_level',
        type='choice',
        dest='log_level',
        default='warn',
        choices=['debug', 'info', 'warn', 'error', 'critical'],
        help='Log level: debug|info|warn(default)|error|critical')
    parser.add_option('-q',
                      '--quiet',
                      dest='quiet',
                      default=False,
                      action='store_true',
                      help='Don\'t print progress messages')
    parser.add_option(
        '--tlimit',
        '--time-limit',
        dest='tlimit',
        type='float',
        default=None,
        help='Record time limit in seconds. Ignored when --dt-sec used.')
    parser.add_option('--launch-delay',
                      '--launch_delay',
                      dest='launch_delay',
                      type='int',
                      default=0,
                      help='Delay (secs) in launching multiple connections')
    parser.add_option(
        '--connect-retries',
        '--connect_retries',
        dest='connect_retries',
        type='int',
        default=0,
        help=
        'Number of retries when connecting to host (retries forever by default)'
    )
    parser.add_option('--connect-timeout',
                      '--connect_timeout',
                      dest='connect_timeout',
                      type='int',
                      default=15,
                      help='Retry timeout(sec) connecting to host')
    parser.add_option('-k',
                      '--socket-timeout',
                      '--socket_timeout',
                      dest='socket_timeout',
                      type='int',
                      default=10,
                      help='Socket timeout(sec) during data transfers')
    parser.add_option(
        '--OV',
        dest='ADC_OV',
        default=False,
        action='store_true',
        help='Print "ADC OV" message when Kiwi ADC is overloaded')
    parser.add_option('-v',
                      '-V',
                      '--version',
                      dest='krec_version',
                      default=False,
                      action='store_true',
                      help='Print version number and exit')

    group = OptionGroup(parser, "Audio connection options", "")
    group.add_option(
        '-f',
        '--freq',
        dest='frequency',
        type='string',
        default=1000,
        help='Frequency to tune to, in kHz (can be a comma-separated list)',
        action='callback',
        callback_args=(float, ),
        callback=get_comma_separated_args)
    group.add_option(
        '-m',
        '--modulation',
        dest='modulation',
        type='string',
        default='am',
        help=
        'Modulation; one of am, lsb, usb, cw, nbfm, iq (default passband if -L/-H not specified)'
    )
    group.add_option('--ncomp',
                     '--no_compression',
                     dest='compression',
                     default=True,
                     action='store_false',
                     help='Don\'t use audio compression')
    group.add_option('-L',
                     '--lp-cutoff',
                     dest='lp_cut',
                     type='float',
                     default=None,
                     help='Low-pass cutoff frequency, in Hz')
    group.add_option('-H',
                     '--hp-cutoff',
                     dest='hp_cut',
                     type='float',
                     default=None,
                     help='High-pass cutoff frequency, in Hz')
    group.add_option(
        '-r',
        '--resample',
        dest='resample',
        type='int',
        default=0,
        help=
        'Resample output file to new sample rate in Hz. The resampling ratio has to be in the range [1/256,256]'
    )
    group.add_option('-T',
                     '--squelch-threshold',
                     dest='thresh',
                     type='float',
                     default=None,
                     help='Squelch threshold, in dB.')
    group.add_option(
        '--squelch-tail',
        dest='squelch_tail',
        type='float',
        default=1,
        help=
        'Time for which the squelch remains open after the signal is below threshold.'
    )
    group.add_option(
        '-g',
        '--agc-gain',
        dest='agc_gain',
        type='string',
        default=None,
        help=
        'AGC gain; if set, AGC is turned off (can be a comma-separated list)',
        action='callback',
        callback_args=(float, ),
        callback=get_comma_separated_args)
    group.add_option('--nb',
                     dest='nb',
                     action='store_true',
                     default=False,
                     help='Enable noise blanker with default parameters.')
    group.add_option(
        '--nb-gate',
        dest='nb_gate',
        type='int',
        default=100,
        help='Noise blanker gate time in usec (100 to 5000, default 100)')
    group.add_option(
        '--nb-th',
        '--nb-thresh',
        dest='nb_thresh',
        type='int',
        default=50,
        help='Noise blanker threshold in percent (0 to 100, default 50)')
    parser.add_option_group(group)

    group = OptionGroup(parser, "Sound device options", "")
    group.add_option(
        '--snddev',
        '--sound-device',
        dest='sounddevice',
        type='string',
        default='',
        action='callback',
        help='Sound device to play kiwi audio on (can be comma separated list)',
        callback_args=(str, ),
        callback=get_comma_separated_args)
    group.add_option('--ls-snd',
                     '--list-sound-devices',
                     dest='list_sound_devices',
                     default=False,
                     action='store_true',
                     help='List available sound devices and exit')
    parser.add_option_group(group)

    group = OptionGroup(parser, "Rig control options", "")
    group.add_option('--rigctl',
                     '--enable-rigctl',
                     dest='rigctl_enabled',
                     default=True,
                     action='store_true',
                     help='Enable rigctld backend for frequency changes.')
    group.add_option(
        '--rigctl-port',
        '--rigctl-port',
        dest='rigctl_port',
        type='string',
        default='6400',
        help=
        'Port listening for rigctl commands (default 6400, can be comma separated list',
        action='callback',
        callback_args=(int, ),
        callback=get_comma_separated_args)
    group.add_option('--rigctl-addr',
                     '--rigctl-address',
                     dest='rigctl_address',
                     type='string',
                     default=None,
                     help='Address to listen on (default 127.0.0.1)')
    parser.add_option_group(group)

    (options, unused_args) = parser.parse_args()

    ## clean up OptionParser which has cyclic references
    parser.destroy()

    if options.krec_version:
        print('kiwiclientd v1.0')
        sys.exit()

    if options.list_sound_devices:
        print(sc.all_speakers())
        sys.exit()

    FORMAT = '%(asctime)-15s pid %(process)5d %(message)s'
    logging.basicConfig(level=logging.getLevelName(options.log_level.upper()),
                        format=FORMAT)
    if options.log_level.upper() == 'DEBUG':
        gc.set_debug(gc.DEBUG_SAVEALL | gc.DEBUG_LEAK | gc.DEBUG_UNCOLLECTABLE)

    run_event = threading.Event()
    run_event.set()

    if options.tlimit is not None and options.dt != 0:
        print('Warning: --tlimit ignored when --dt-sec option used')

    options.sdt = 0
    options.dir = None
    options.raw = False
    options.sound = True
    options.no_api = False
    options.tstamp = False
    options.station = None
    options.filename = None
    options.test_mode = False
    options.is_kiwi_wav = False
    options.is_kiwi_tdoa = False
    gopt = options
    multiple_connections, options = options_cross_product(options)

    snd_recorders = []
    for i, opt in enumerate(options):
        opt.multiple_connections = multiple_connections
        opt.idx = i
        snd_recorders.append(
            KiwiWorker(args=(KiwiSoundRecorder(opt), opt, run_event)))

    try:
        for i, r in enumerate(snd_recorders):
            if opt.launch_delay != 0 and i != 0 and options[
                    i - 1].server_host == options[i].server_host:
                time.sleep(opt.launch_delay)
            r.start()
            #logging.info("started kiwi client %d, timestamp=%d" % (i, options[i].timestamp))
            logging.info("started kiwi client %d" % i)

        while run_event.is_set():
            time.sleep(.1)

    except KeyboardInterrupt:
        run_event.clear()
        join_threads(snd_recorders)
        print("KeyboardInterrupt: threads successfully closed")
    except Exception as e:
        print_exc()
        run_event.clear()
        join_threads(snd_recorders)
        print("Exception: threads successfully closed")

    logging.debug('gc %s' % gc.garbage)
Exemplo n.º 18
0
def test_speakers():
    for speaker in soundcard.all_speakers():
        assert isinstance(speaker.name, str)
        assert hasattr(speaker, 'id')
        assert isinstance(speaker.channels, int)
        assert speaker.channels > 0
Exemplo n.º 19
0
def first_code():
    speakers = sc.all_speakers()
    defspeaker = sc.default_speaker()
    print(speakers)
    print(defspeaker)
Exemplo n.º 20
0
 def select_audio_mixers(self):
     self._input_mixer = sc.all_speakers()[0] if len(
         sc.all_speakers()) > 0 else None
     self._output_mixer = sc.all_microphones()[0] if len(
         sc.all_microphones()) > 0 else None
Exemplo n.º 21
0
def start_mixer():

    all_speakers = sc.all_speakers()
    (spkr1, spkr2) = filter(speaker_name_matcher, all_speakers)

    all_inputs = sc.all_microphones()
    (mic1, mic2) = filter(mic_name_matcher, all_inputs)

    input1 = mic1.recorder(samplerate=SAMPLE_RATE, blocksize=BLOCK_SIZE)
    input1.__enter__()

    input2 = mic2.recorder(samplerate=SAMPLE_RATE, blocksize=BLOCK_SIZE)
    input2.__enter__()

    lb = find_loopback_port()
    opendsh = lb.recorder(samplerate=SAMPLE_RATE, blocksize=BLOCK_SIZE)
    opendsh.__enter__()

    output1 = spkr1.player(samplerate=SAMPLE_RATE)
    output1.__enter__()

    output2 = spkr2.player(samplerate=SAMPLE_RATE)
    output2.__enter__()

    player = None
    playing_count = 0

    while True:

        playing = None

        i1 = input1.record(numframes=NUM_FRAMES)
        i2 = input2.record(numframes=NUM_FRAMES)
        od = opendsh.record(numframes=NUM_FRAMES)

        if player == Inputs.CHANNEL1 and playing_count < MIN_PLAYING_COUNT:
            output1.play(i1)
            output2.play(i1)
            playing = Inputs.CHANNEL1
        elif player == Inputs.CHANNEL2 and playing_count < MIN_PLAYING_COUNT:
            output1.play(i2)
            output2.play(i2)
            playing = Inputs.CHANNEL2
        elif player == Inputs.OPENDSH and playing_count < MIN_PLAYING_COUNT:
            output1.play(od)
            output2.play(od)
            playing = Inputs.OPENDSH
        elif not is_silent(i1):
            output1.play(i1)
            output2.play(i1)
            playing = Inputs.CHANNEL1
        elif not is_silent(i2):
            output1.play(i2)
            output2.play(i2)
            playing = Inputs.CHANNEL2
        else:
            output1.play(od)
            output2.play(od)
            playing = Inputs.OPENDSH

        if playing != player:
            logger.debug("switching to player: {}".format(playing))
            player = playing
            playing_count = 0
        else:
            playing_count += 1
Exemplo n.º 22
0
        os.system(screen_clear)
        lines.reverse()
        for l in lines:
            print(l)

    def start_render_loop(self):
        while True:
            self._render()
            time.sleep(0.01667)


if __name__ == '__main__':

    import soundcard as sc

    from ..common import samplegen

    selections = sc.all_speakers()
    for i, s in enumerate(selections):
        print("{} : {}".format(i, s))

    s = int(input("Speaker Choice: "))
    s_name = selections[s].name

    mixin = sc.get_microphone(id=s_name, include_loopback=True)

    with mixin.recorder(samplerate=44100) as rec:
        ar = AudioRenderer(samplegen(rec), char="|")
        ar.start_render_loop()
Exemplo n.º 23
0
import soundcard as sc
import struct

BLOCKSIZE = 512
NUMFRAMES = 256

mics = sc.all_microphones(include_loopback=True)
mic = mics[5]
print(mic)
recorder = mic.recorder(samplerate=48000, blocksize=BLOCKSIZE)
recorder.__enter__()

spkrs = sc.all_speakers()
spkr = spkrs[0]
print(spkr)
player = spkr.player(samplerate=48000, blocksize=BLOCKSIZE)
player.__enter__()

while True:
    data = recorder.record(numframes=NUMFRAMES)
    player.play(data)

recorder.__exit__()
player.__exit__()
                    arg = arg.split("=")
                    self.kflags[arg[0]] = '='.join(arg[1:])

                # Is a flag
                else:
                    self.flags.append(arg)


# Parse shell arguments TODO: proper interface using typer?
# Temporary file mostly?
args = ArgParser(sys.argv)

# List and override soundcard outputs
if ("list" in args.flags):
    print(f"{debug_prefix} Available devices to play audio:")
    for index, device in enumerate(soundcard.all_speakers()):
        print(f"{debug_prefix} > ({index}) [{device.name}]")
    print(
        f"{debug_prefix} :: Run this script again with [play=N] flag to override"
    )
    sys.exit(0)

# Overriding capture stuff
output_sound_card = args.kflags.get("play", None)
override_record_device = None

if output_sound_card is not None:
    recordable = soundcard.all_speakers()

    # Error assertion
    if (len(recordable) < int(cap)) or (int(cap) < 0):