recs 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
recs/__main__.py CHANGED
@@ -1,6 +1,29 @@
1
- if __name__ == '__main__':
2
- import sys
1
+ import sys
2
+
3
+ import click
4
+
5
+ from recs.base import RecsError
6
+ from recs.cfg import app, cli
7
+
8
+ assert cli
9
+
10
+
11
+ def run() -> int:
12
+ try:
13
+ app.app(prog_name='recs', standalone_mode=False)
14
+ return 0
3
15
 
4
- from recs.cfg import cli
16
+ except RecsError as e:
17
+ print('ERROR:', *e.args, file=sys.stderr)
5
18
 
6
- sys.exit(cli.run())
19
+ except click.ClickException as e:
20
+ print(f'{e.__class__.__name__}: {e.message}', file=sys.stderr)
21
+
22
+ except click.Abort:
23
+ print('Interrupted', file=sys.stderr)
24
+
25
+ return -1
26
+
27
+
28
+ if __name__ == '__main__':
29
+ sys.exit(run())
recs/audio/block.py CHANGED
@@ -37,7 +37,7 @@ class Block:
37
37
 
38
38
  @cached_property
39
39
  def volume(self) -> float:
40
- return sum(self.amplitude) / len(self.amplitude) / self.scale
40
+ return sum(self.amplitude) / len(self.amplitude)
41
41
 
42
42
  @cached_property
43
43
  def channel_count(self) -> int:
@@ -45,7 +45,7 @@ class Block:
45
45
 
46
46
  @cached_property
47
47
  def amplitude(self) -> np.ndarray:
48
- return (self.max - self.min) / 2
48
+ return (self.max - self.min) / (2 * self.scale)
49
49
 
50
50
  @cached_property
51
51
  def max(self) -> np.ndarray:
@@ -99,6 +99,3 @@ class Blocks:
99
99
 
100
100
  def __getitem__(self, i: int) -> Block:
101
101
  return self.blocks[i]
102
-
103
- def __len__(self) -> int:
104
- return len(self.blocks)
@@ -1,17 +1,18 @@
1
+ import contextlib
1
2
  import typing as t
2
3
  from datetime import datetime
3
4
  from pathlib import Path
4
5
  from threading import Lock
5
6
 
7
+ from overrides import override
6
8
  from soundfile import SoundFile
7
9
  from threa import Runnable
8
10
 
9
- from recs.base import times
11
+ from recs.base.state import ChannelState
10
12
  from recs.base.types import SDTYPE, Active, Format, SdType
11
- from recs.cfg import Track
12
- from recs.misc import file_list
13
+ from recs.cfg import Cfg, Track, device, time_settings
14
+ from recs.misc import counter, file_list
13
15
 
14
- from ..cfg.cfg import Cfg
15
16
  from .block import Block, Blocks
16
17
  from .file_opener import FileOpener
17
18
  from .header_size import header_size
@@ -27,7 +28,6 @@ FORMAT_TO_SIZE_LIMIT = {
27
28
  ITEMSIZE = {
28
29
  SdType.float32: 4,
29
30
  SdType.int16: 2,
30
- SdType.int24: 3,
31
31
  SdType.int32: 4,
32
32
  }
33
33
 
@@ -35,20 +35,15 @@ BLOCK_FUZZ = 2
35
35
 
36
36
 
37
37
  class ChannelWriter(Runnable):
38
- blocks_seen: int = 0
39
- blocks_written: int = 0
40
38
  bytes_in_this_file: int = 0
41
39
 
42
40
  frames_in_this_file: int = 0
43
- frames_seen: int = 0
44
- frame_size: int = 0
45
- frames_written: int = 0
41
+ frames_written: int = 0 # Used elsewhere
46
42
 
47
43
  largest_file_size: int = 0
48
44
  longest_file_frames: int = 0
49
45
 
50
46
  timestamp: float = 0
51
- tracknumber: int = 0
52
47
 
53
48
  _sf: SoundFile | None = None
54
49
 
@@ -56,10 +51,12 @@ class ChannelWriter(Runnable):
56
51
  def active(self) -> Active:
57
52
  return Active.active if self._sf else Active.inactive
58
53
 
59
- def __init__(self, cfg: Cfg, times: times.TimeSettings[int], track: Track) -> None:
54
+ def __init__(
55
+ self, cfg: Cfg, times: time_settings.TimeSettings[int], track: Track
56
+ ) -> None:
60
57
  super().__init__()
61
58
 
62
- self.dry_run = cfg.dry_run
59
+ self.do_not_record = cfg.dry_run or cfg.calibrate
63
60
  self.format = cfg.format
64
61
  self.metadata = cfg.metadata
65
62
  self.times = times
@@ -72,51 +69,94 @@ class ChannelWriter(Runnable):
72
69
  self.frame_size = ITEMSIZE[cfg.sdtype or SDTYPE] * len(track.channels)
73
70
  self.longest_file_frames = times.longest_file_time
74
71
  self.opener = FileOpener(cfg, track)
72
+ self._volume = counter.MovingBlock(times.moving_average_time)
75
73
 
76
74
  if not cfg.infinite_length:
77
75
  largest = FORMAT_TO_SIZE_LIMIT.get(cfg.format, 0)
78
76
  self.largest_file_size = max(largest - BUFFER, 0)
79
77
 
78
+ def update(self, update: device.Update) -> ChannelState:
79
+ block = Block(update.array[:, self.track.slice])
80
+ with self._lock:
81
+ return self._receive_block(block, update.timestamp)
82
+
83
+ @override
80
84
  def stop(self) -> None:
81
85
  with self._lock:
82
- self.running.clear()
86
+ self.running = False
83
87
  self._write_and_close()
84
- self.stopped.set()
88
+ self.stopped = True
85
89
 
86
- def write(self, block: Block, timestamp: float) -> None:
87
- self.frames_seen += len(block)
88
- self.blocks_seen += 1
90
+ def _close(self) -> None:
91
+ sf, self._sf = self._sf, None
92
+ if sf and sf.frames and sf.frames >= self.times.shortest_file_time:
93
+ sf.close()
94
+ elif sf:
95
+ with contextlib.suppress(Exception):
96
+ sf.close()
97
+ with contextlib.suppress(Exception):
98
+ Path(sf.name).unlink()
89
99
 
90
- if not self.dry_run and (self.running or self._sf):
91
- dt = self.timestamp - timestamp
92
- self.timestamp = timestamp
100
+ def _open(self, offset: int) -> SoundFile:
101
+ timestamp = self.timestamp - offset / self.track.device.samplerate
102
+ ts = datetime.fromtimestamp(timestamp)
93
103
 
94
- with self._lock:
95
- self._write(block, dt)
104
+ index = 1 + len(self.files_written)
96
105
 
97
- def _write(self, block: Block, dt: float):
98
- expected_dt = len(block) / self.track.device.samplerate
106
+ metadata = {'date': ts.isoformat(), 'software': URL, 'tracknumber': str(index)}
107
+ metadata |= self.metadata
99
108
 
100
- if dt > expected_dt * BLOCK_FUZZ:
101
- # We were asleep, or otherwise lost time.
102
- self._write_and_close()
109
+ self.bytes_in_this_file = header_size(metadata, self.format)
110
+ self.frames_in_this_file = 0
103
111
 
104
- self._blocks.append(block)
112
+ sf = self.opener.create(metadata, timestamp, index)
113
+ self.files_written.append(Path(sf.name))
114
+ return sf
105
115
 
106
- if block.volume >= self.times.noise_floor_amplitude:
107
- if not self._sf:
108
- # Record a little quiet before the first block
109
- length = self.times.quiet_before_start + len(self._blocks[-1])
110
- self._blocks.clip(length, from_start=True)
116
+ def _receive_block(self, block: Block, timestamp: float) -> ChannelState:
117
+ saved_state = self._state(
118
+ max_amp=max(block.max) / block.scale,
119
+ min_amp=min(block.min) / block.scale,
120
+ )
111
121
 
112
- self._write_blocks(self._blocks)
113
- self._blocks.clear()
122
+ dt = self.timestamp - timestamp
123
+ self.timestamp = timestamp
124
+ self._volume(block)
114
125
 
115
- if not self.running or self._blocks.duration > self.times.stop_after_quiet:
116
- self._write_and_close()
126
+ if not self.do_not_record and (self._sf or not self.stopped):
127
+ expected_dt = len(block) / self.track.device.samplerate
128
+
129
+ if dt > expected_dt * BLOCK_FUZZ: # We were asleep, or otherwise lost time
130
+ self._write_and_close()
131
+
132
+ self._blocks.append(block)
133
+
134
+ if block.volume >= self.times.noise_floor_amplitude:
135
+ if not self._sf: # Record some quiet before the first block
136
+ length = self.times.quiet_before_start + len(self._blocks[-1])
137
+ self._blocks.clip(length, from_start=True)
138
+
139
+ self._write_blocks(self._blocks)
140
+ self._blocks.clear()
141
+
142
+ if self.stopped or self._blocks.duration > self.times.stop_after_quiet:
143
+ self._write_and_close()
144
+
145
+ return self._state() - saved_state
146
+
147
+ def _state(self, **kwargs) -> ChannelState:
148
+ return ChannelState(
149
+ file_count=len(self.files_written),
150
+ file_size=self.files_written.total_size,
151
+ is_active=bool(self._sf),
152
+ recorded_time=self.frames_written / self.track.device.samplerate,
153
+ timestamp=self.timestamp,
154
+ volume=tuple(self._volume.mean()),
155
+ **kwargs,
156
+ )
117
157
 
118
158
  def _write_and_close(self) -> None:
119
- # Record a little quiet after the last block
159
+ # Record some quiet after the last block
120
160
  removed = self._blocks.clip(self.times.quiet_after_end, from_start=False)
121
161
 
122
162
  if self._sf and removed:
@@ -124,15 +164,13 @@ class ChannelWriter(Runnable):
124
164
 
125
165
  self._close()
126
166
 
127
- def _close(self) -> None:
128
- if self._sf:
129
- self._sf.close()
130
- if self._sf.frames <= self.times.shortest_file_time:
131
- if (p := Path(self._sf.name)).exists():
132
- p.unlink()
133
- self._sf = None
134
-
135
- def _write_blocks(self, blocks: t.Iterable[Block]) -> None:
167
+ def _write_blocks(self, blox: t.Iterable[Block]) -> None:
168
+ blocks = list(blox)
169
+
170
+ # The last block in the list ends at self.timestamp so
171
+ # we keep track of the sample offset before that
172
+ offset = -sum(len(b) for b in blocks)
173
+
136
174
  for b in blocks:
137
175
  # Check if this block will overrun the file size or length
138
176
  remains: list[int] = []
@@ -140,39 +178,17 @@ class ChannelWriter(Runnable):
140
178
  if self.longest_file_frames:
141
179
  remains.append(self.longest_file_frames - self.frames_in_this_file)
142
180
 
143
- if self.largest_file_size and self._sf:
181
+ if self._sf and self.largest_file_size:
144
182
  file_bytes = self.largest_file_size - self.bytes_in_this_file
145
183
  remains.append(file_bytes // self.frame_size)
146
184
 
147
- if remains and (r := min(remains)) <= len(b):
148
- self._write_one(b[:r])
185
+ if remains and min(remains) <= len(b):
149
186
  self._close()
150
- b = b[r:]
151
-
152
- if b:
153
- self._write_one(b)
154
-
155
- def _write_one(self, b: Block) -> None:
156
- if not self._sf:
157
- self.tracknumber += 1
158
- t = str(self.tracknumber)
159
- # TODO: the timestamp will be a bit late for this block
160
- # because self.timestamp is the time of the last block
161
- # in the list!
162
- ts = datetime.fromtimestamp(self.timestamp)
163
- metadata = dict(date=ts.isoformat(), software=URL, tracknumber=t)
164
- metadata |= self.metadata
165
-
166
- self._sf = self.opener.create(metadata, timestamp=self.timestamp)
167
- self.bytes_in_this_file = header_size(metadata, self.format)
168
-
169
- self.files_written.append(Path(self._sf.name))
170
- self.frames_in_this_file = 0
171
-
172
- self._sf.write(b.block)
173
187
 
174
- self.blocks_written += 1
175
- self.frames_in_this_file += len(b)
176
- self.frames_written += len(b)
188
+ self._sf = self._sf or self._open(offset)
189
+ self._sf.write(b.block)
190
+ offset += len(b)
177
191
 
178
- self.bytes_in_this_file += len(b) * self.frame_size
192
+ self.frames_in_this_file += len(b)
193
+ self.frames_written += len(b)
194
+ self.bytes_in_this_file += len(b) * self.frame_size
recs/audio/file_opener.py CHANGED
@@ -1,9 +1,10 @@
1
+ import itertools
1
2
  from pathlib import Path
2
3
 
3
4
  import soundfile as sf
4
5
 
5
6
  from recs.cfg import Cfg, Track
6
- from recs.misc.recording_path import recording_path
7
+ from recs.cfg.metadata import ALLOWS_METADATA
7
8
 
8
9
 
9
10
  class FileOpener:
@@ -27,23 +28,21 @@ class FileOpener:
27
28
  subtype=self.cfg.subtype,
28
29
  )
29
30
 
30
- for k, v in metadata.items():
31
- setattr(fp, k, v)
31
+ if self.cfg.format in ALLOWS_METADATA:
32
+ for k, v in metadata.items():
33
+ setattr(fp, k, v)
32
34
 
33
35
  return fp
34
36
 
35
- def create(self, metadata: dict[str, str], timestamp: float) -> sf.SoundFile:
36
- index = 0
37
- suffix = ''
38
- path, name = recording_path(
39
- self.track, self.cfg.aliases, self.cfg.subdirectory, timestamp
40
- )
37
+ def create(
38
+ self, metadata: dict[str, str], timestamp: float, index: int
39
+ ) -> sf.SoundFile:
40
+ p = Path(self.cfg.path.evaluate(self.track, self.cfg.aliases, timestamp, index))
41
+ p.parent.mkdir(exist_ok=True, parents=True)
41
42
 
42
- while True:
43
- p = self.cfg.path / path / (name + suffix)
44
- p.parent.mkdir(exist_ok=True, parents=True)
43
+ for i in itertools.count():
44
+ f = p.parent / (p.name + bool(i) * f'_{i}')
45
45
  try:
46
- return self.open(p, metadata)
46
+ return self.open(f, metadata)
47
47
  except FileExistsError:
48
- index += 1
49
- suffix = f'_{index}'
48
+ pass
@@ -0,0 +1,21 @@
1
+ """
2
+ Print the current devices as JSON without loading any other part of recs.
3
+
4
+ Called repeatedly as a subprocess to detect devices going off- and online.
5
+ """
6
+
7
+ import json
8
+ import typing as t
9
+
10
+
11
+ def query_devices() -> list[dict[str, t.Any]]:
12
+ try:
13
+ import sounddevice
14
+
15
+ return sounddevice.query_devices()
16
+ except Exception:
17
+ return []
18
+
19
+
20
+ if __name__ == '__main__':
21
+ print(json.dumps(query_devices(), indent=4))
recs/base/cfg_raw.py CHANGED
@@ -11,15 +11,15 @@ class CfgRaw:
11
11
  #
12
12
  # Directory settings
13
13
  #
14
- path: Path = Path()
15
- subdirectory: t.Sequence[str] = ()
14
+ path: str = ''
16
15
  #
17
16
  # General purpose settings
18
17
  #
18
+ calibrate: bool = False
19
19
  dry_run: bool = False
20
+ verbose: bool = False
20
21
  info: bool = False
21
22
  list_types: bool = False
22
- verbose: bool = False
23
23
  #
24
24
  # Aliases for input devices or channels
25
25
  #
@@ -40,19 +40,21 @@ class CfgRaw:
40
40
  #
41
41
  # Console and UI settings
42
42
  #
43
+ clear: bool = False
43
44
  silent: bool = False
44
- retain: bool = True
45
- ui_refresh_rate: float = 23
46
- sleep_time: float = 0.013
45
+ sleep_time_device: float = 0.1
46
+ ui_refresh_rate: float = 23.0
47
47
  #
48
48
  # Settings relating to times
49
49
  #
50
50
  infinite_length: bool = False
51
- longest_file_time: str = '0' # In HH:MM:SS.SSSS
52
- moving_average_time: float = 1
53
- noise_floor: float = 70
54
- shortest_file_time: str = '1' # In HH:MM:SS.SSSS
55
- quiet_after_end: float = 2
56
- quiet_before_start: float = 1
57
- stop_after_quiet: float = 20
58
- total_run_time: float = 0
51
+ longest_file_time: float = 0.0
52
+ moving_average_time: float = 1.0
53
+ noise_floor: float = 70.0
54
+ shortest_file_time: float = 1.0
55
+ quiet_after_end: float = 2.0
56
+ quiet_before_start: float = 1.0
57
+ stop_after_quiet: float = 20.0
58
+ total_run_time: float = 0.0
59
+
60
+ asdict = dc.asdict
recs/base/prefix_dict.py CHANGED
@@ -8,20 +8,19 @@ class PrefixDict(dict[str, T]):
8
8
  try:
9
9
  return super().__getitem__(key)
10
10
  except KeyError:
11
- if not key:
12
- raise
13
- key = key.strip().lower()
11
+ pass
12
+
13
+ error = 'unknown'
14
+ if key := key.strip().lower():
14
15
  try:
15
16
  return super().__getitem__(key)
16
17
  except KeyError:
17
18
  pass
18
19
 
19
- if 1 == len(m := [v for k, v in self.items() if k.lower().startswith(key)]):
20
- return m[0]
21
- raise
20
+ matches = [v for k, v in self.items() if k.lower().startswith(key)]
21
+ if len(matches) == 1:
22
+ return matches[0]
23
+ if matches:
24
+ error = 'ambiguous'
22
25
 
23
- def get_value(self, key: str, default: T | None = None) -> T | None:
24
- try:
25
- return self[key]
26
- except KeyError:
27
- return default
26
+ raise KeyError(key, error)
recs/base/state.py ADDED
@@ -0,0 +1,73 @@
1
+ import dataclasses as dc
2
+ import time
3
+
4
+ from recs.cfg.time_settings import amplitude_to_db
5
+
6
+ INF = float('inf')
7
+
8
+
9
+ @dc.dataclass(slots=True)
10
+ class ChannelState:
11
+ """Represents the state of a single recording channel"""
12
+
13
+ condition: str = 'running'
14
+
15
+ file_count: int = 0
16
+ file_size: int = 0
17
+
18
+ is_active: bool = False
19
+
20
+ max_amp: float = -INF
21
+ min_amp: float = INF
22
+
23
+ recorded_time: float = 0
24
+ timestamp: float = dc.field(default_factory=time.time)
25
+ volume: tuple[float, ...] = ()
26
+
27
+ replace = dc.replace
28
+
29
+ @property
30
+ def amp(self) -> float:
31
+ if self.max_amp == -INF or self.min_amp == INF:
32
+ return 0
33
+ return (self.max_amp - self.min_amp) / 2
34
+
35
+ @property
36
+ def db_range(self) -> float:
37
+ return amplitude_to_db(self.amp)
38
+
39
+ def __iadd__(self, m: 'ChannelState') -> 'ChannelState':
40
+ self.file_count += m.file_count
41
+ self.file_size += m.file_size
42
+ self.recorded_time += m.recorded_time
43
+
44
+ # We copy these three when using +=, but not -=!
45
+ self.condition = m.condition
46
+ self.is_active = m.is_active
47
+ self.timestamp = m.timestamp
48
+ self.volume = m.volume
49
+
50
+ self.max_amp = max(self.max_amp, m.max_amp)
51
+ self.min_amp = min(self.min_amp, m.min_amp)
52
+
53
+ return self
54
+
55
+ def __isub__(self, m: 'ChannelState') -> 'ChannelState':
56
+ self.file_count -= m.file_count
57
+ self.file_size -= m.file_size
58
+ self.recorded_time -= m.recorded_time
59
+
60
+ self.max_amp = max(self.max_amp, m.max_amp)
61
+ self.min_amp = min(self.min_amp, m.min_amp)
62
+
63
+ return self
64
+
65
+ def __add__(self, m: 'ChannelState') -> 'ChannelState':
66
+ x = self.replace()
67
+ x += m
68
+ return x
69
+
70
+ def __sub__(self, m: 'ChannelState') -> 'ChannelState':
71
+ x = self.replace()
72
+ x -= m
73
+ return x
recs/base/times.py CHANGED
@@ -1,66 +1,7 @@
1
- import dataclasses as dc
2
- import math
3
- import time as _time
4
- import typing as t
5
- from functools import cached_property
1
+ import time
6
2
 
7
- T = t.TypeVar('T', float, int)
8
- NO_SCALE = ('noise_floor',)
9
-
10
- sleep = _time.sleep
11
- time = _time.time
12
-
13
-
14
- def db_to_amplitude(db: float) -> float:
15
- return 10 ** (-db / 20)
16
-
17
-
18
- def amplitude_to_db(amp: float) -> float:
19
- return -20 * math.log10(amp)
20
-
21
-
22
- @dc.dataclass(frozen=True)
23
- class TimeSettings(t.Generic[T]):
24
- """Amounts of time are specified as seconds in the input but converted
25
- to samples when we find out the sample rate
26
- """
27
-
28
- #: Longest amount of time per file: 0 means infinite
29
- longest_file_time: T = t.cast(T, 0)
30
-
31
- #: Shortest amount of time per file
32
- shortest_file_time: T = t.cast(T, 0)
33
-
34
- #: Amount of quiet at the start
35
- quiet_before_start: T = t.cast(T, 0)
36
-
37
- #: Amount of quiet at the end
38
- quiet_after_end: T = t.cast(T, 0)
39
-
40
- #: Amount of quiet before stopping a recording
41
- stop_after_quiet: T = t.cast(T, 0)
42
-
43
- # Time for moving averages for the meters
44
- moving_average_time: T = t.cast(T, 0)
45
-
46
- #: The noise floor in decibels
47
- noise_floor: float = 70
48
-
49
- #: Amount of total time to run. 0 or less means "run forever"
50
- total_run_time: T = t.cast(T, 0)
51
-
52
- @cached_property
53
- def noise_floor_amplitude(self) -> float:
54
- return db_to_amplitude(self.noise_floor)
55
-
56
- def __post_init__(self):
57
- if negative_fields := [k for k, v in dc.asdict(self).items() if v < 0]:
58
- raise ValueError(f'TimeSettings cannot be negative: {negative_fields=}')
59
-
60
- def scale(self, samplerate: float | int) -> 'TimeSettings[int]':
61
- it = dc.asdict(self).items()
62
- d = {k: v if k in NO_SCALE else round(samplerate * v) for k, v in it}
63
- return TimeSettings[int](**d)
3
+ sleep = time.sleep
4
+ timestamp = time.time
64
5
 
65
6
 
66
7
  def to_time(t: str) -> float:
@@ -2,26 +2,27 @@ from .prefix_dict import PrefixDict
2
2
  from .types import Format, SdType, Subtype
3
3
 
4
4
  FORMATS = PrefixDict[Format]({str(s): s for s in Format})
5
+ SDTYPES = PrefixDict[SdType]({str(s): s for s in SdType})
5
6
  SUBTYPES = PrefixDict[Subtype]({str(s): s for s in Subtype})
6
7
 
7
8
  SUBTYPE_TO_SDTYPE = {
8
9
  Subtype.alac_16: SdType.int16,
9
10
  Subtype.alac_20: SdType.int32,
10
- Subtype.alac_24: SdType.int24,
11
+ Subtype.alac_24: SdType.int32,
11
12
  Subtype.alac_32: SdType.int32,
12
13
  Subtype.double: SdType.float32,
13
14
  Subtype.dpcm_16: SdType.int16,
14
15
  Subtype.dpcm_8: SdType.int16,
15
16
  Subtype.dwvw_12: SdType.int16,
16
17
  Subtype.dwvw_16: SdType.int16,
17
- Subtype.dwvw_24: SdType.int24,
18
+ Subtype.dwvw_24: SdType.int32,
18
19
  Subtype.float: SdType.float32,
19
20
  Subtype.ms_adpcm: SdType.int32,
20
21
  Subtype.nms_adpcm_16: SdType.int16,
21
- Subtype.nms_adpcm_24: SdType.int24,
22
+ Subtype.nms_adpcm_24: SdType.int32,
22
23
  Subtype.nms_adpcm_32: SdType.int32,
23
24
  Subtype.pcm_16: SdType.int16,
24
- Subtype.pcm_24: SdType.int24,
25
+ Subtype.pcm_24: SdType.int32,
25
26
  Subtype.pcm_32: SdType.int32,
26
27
  Subtype.pcm_s8: SdType.int16,
27
28
  Subtype.pcm_u8: SdType.int16,
@@ -29,7 +30,6 @@ SUBTYPE_TO_SDTYPE = {
29
30
 
30
31
  SDTYPE_TO_SUBTYPE = {
31
32
  SdType.int16: Subtype.pcm_16,
32
- SdType.int24: Subtype.pcm_24,
33
33
  SdType.int32: Subtype.pcm_32,
34
34
  SdType.float32: Subtype.float,
35
35
  }
recs/base/types.py CHANGED
@@ -1,8 +1,10 @@
1
+ import typing as t
1
2
  from enum import auto
2
3
 
3
4
  from strenum import StrEnum
4
5
 
5
6
  DeviceDict = dict[str, float | int | str]
7
+ Stop = t.Callable[[], None]
6
8
 
7
9
 
8
10
  class Active(StrEnum):
@@ -14,7 +16,6 @@ class Active(StrEnum):
14
16
  class SdType(StrEnum):
15
17
  float32 = auto()
16
18
  int16 = auto()
17
- int24 = auto()
18
19
  int32 = auto()
19
20
 
20
21
 
@@ -80,9 +81,3 @@ class Subtype(StrEnum):
80
81
  ulaw = auto()
81
82
  vorbis = auto()
82
83
  vox_adpcm = auto()
83
-
84
-
85
- class Subdirectory(StrEnum):
86
- channel = auto()
87
- device = auto()
88
- time = auto()