allendowney / thinkdsp Goto Github PK
View Code? Open in Web Editor NEWThink DSP: Digital Signal Processing in Python, by Allen B. Downey.
Home Page: https://allendowney.github.io/ThinkDSP/
Think DSP: Digital Signal Processing in Python, by Allen B. Downey.
Home Page: https://allendowney.github.io/ThinkDSP/
it should be ts = np.linspace(0, 1, framerate+1)
in order to preserve
framerate = 1/(ts[1] - ts[0])
for example in 6.1
phi gets rendered differently in different contexts.
For example, on page 31, phi looks different in an exponent or inline.
One option is to use exp() as a function everywhere, rather than e^{exponent}
Line 124 in eeef574
In read_wave(), the call to normalize() makes issue: if read_wave() then write(), the new audio is different in amp.
Suggest don't call normalize(), but convert data to [-1.0, 1.0] using: ys = ys * 1.0 / ( 2 ** (sampwidth * 8 - 1));
wave.normalize()
A minor point on reading about Discrete cosine transform chapter, there are two test function describe to build an M matrix (see bellow). The amp array is not used in any of them so it should be remove to avoid confusion.
Source: https://github.com/AllenDowney/ThinkDSP/blob/master/code/chap06.ipynb
def test1():
amps = np.array([0.6, 0.25, 0.1, 0.05])
N = 4.0
time_unit = 0.001
ts = np.arange(N) / N * time_unit
max_freq = N / time_unit / 2
fs = np.arange(N) / N * max_freq
args = np.outer(ts, fs)
M = np.cos(PI2 * args)
return M
def test2():
amps = np.array([0.6, 0.25, 0.1, 0.05])
N = 4.0
ts = (0.5 + np.arange(N)) / N
fs = (0.5 + np.arange(N)) / 2
args = np.outer(ts, fs)
M = np.cos(PI2 * args)
return M
M = test2()
M
I very much admire that you put all the sources of the book and all the code online, that's really great!
I'm sure this is a great resource for many readers and hackers, as the stars and forks already indicate.
Since you stated in the README that this is "work in progress", I took the liberty of making a few comments:
Your thinkdsp
module sure is well-intended, but I think it causes severe lock-in and it will, however convenient in the beginning, at some point be an obstacle for readers who learn Python with your book (which I hope there are many of!).
Your Wave
class may seem like a good idea, but I honestly think it's not.
It may seem convenient, but I think it's not as convenient as it's restricting.
It would be more straightforward and future-proof if you just handle your signals as NumPy arrays.
All your functions returning signals should return NumPy arrays and instead of methods of the Wave
class you should have free functions to manipulate the signals.
IMHO, this has the one disadvantage that you have to take care of the sampling rate by hand, but this is really something everyone using DSP should learn from the beginning.
So even that may be construed as an advantage, since the sampling rate has to be handled explicitly instead of implicitly (and we all know what the Zen of Python says about that!).
Your Signal
family of classes might be justified, but probably you'd be also better off just making functions that return NumPy arrays.
Finally, let me make a shameless plug for 2 libraries I've been working on:
https://github.com/bastibe/PySoundFile
https://github.com/spatialaudio/python-sounddevice
Both are very easy to install via pip (on Windows/OSX/Linux) and they should give you more comprehensive functionality for reading/writing sound files and playing/recording sound, respectively.
You should give them a try!
Are there any plans to make these codes compatible with Python3?
def analyze2(ys, fs, ts)
N is not defined
Hello AllenDowney,
I was using your module and it is really a fabulous work done by you.
But I am facing a problem like this module can't read the mp3 audio file .
If you are upgrading the module please add more audio file extensions readable.
Hiya,
In section 0.2 of the book (PDF version**), the anaconda distro download link is broken (url was http://continuum.io/downloads.). Current distro can be found at https://www.anaconda.com/distribution/
Thanks for the book Prof!
**PDF version found at: http://greenteapress.com/thinkdsp/thinkdsp.pdf
I wanted to build the HTML version of the book, to try and convert it into an EPUB version, but that failed with the following error (when running make hevea
):
sed 's/\(figs\/[^.]*\).\(pdf\|png\)/\1.eps/' book.tex > thinkdsp.tex
rm -rf html
mkdir html
hevea -O -e latexonly htmlonly thinkdsp
Exclude comment 'comment'
/usr/lib/hevea/hyperref.hva:65: Warning: Ignoring option: 'bookmarks'
./thinkdsp.tex:68: Warning: Command not found: \topsep
./thinkdsp.tex:69: Warning: Command not found: \topsep
./thinkdsp.tex:5203: Warning: '_' occurring outside math mode
./thinkdsp.tex:5223: Warning: '_' occurring outside math mode
./thinkdsp.tex:5250: Warning: '_' occurring outside math mode
./thinkdsp.tex:5374: Warning: '_' occurring outside math mode
./thinkdsp.tex:5420: Warning: '_' occurring outside math mode
./thinkdsp.tex:5479: Warning: '_' occurring outside math mode
./thinkdsp.tex:5523: Warning: '_' occurring outside math mode
./thinkdsp.tex:5555: Warning: '_' occurring outside math mode
./thinkdsp.tex:5578: Warning: '_' occurring outside math mode
./thinkdsp.tex:6830: Warning: Command not found: \backmatter
HeVeA Warning: images may have changed, run 'imagen thinkdsp'
thinkdsp.html:488:143: Warning, block level element: div nested inside text-level element
thinkdsp.html:488:142: This opening tag is pending
thinkdsp.html:748:143: Warning, block level element: div nested inside text-level element
thinkdsp.html:748:142: This opening tag is pending
thinkdsp.html:1093:143: Warning, block level element: div nested inside text-level element
thinkdsp.html:1093:142: This opening tag is pending
thinkdsp.html:2999:126: Parser error: Unexpected </span>
thinkdsp.html:3854:369: Lexer error: extract_tag
thinkdsp.html:3854:369: Lexer error: abstract_tag
grep -v latexonly thinkdsp.image.tex > a; mv a thinkdsp.image.tex
grep -v fancyhdr thinkdsp.image.tex > a; mv a thinkdsp.image.tex
imagen -png thinkdsp
RESOLUTION: 100
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Arch Linux) (preloaded format=latex)
restricted \write18 enabled.
entering extended mode
(./thinkdsp.image.tex
LaTeX2e <2017-04-15>
Babel <3.15> and hyphenation patterns for 84 language(s) loaded.
(/usr/share/texmf-dist/tex/latex/base/book.cls
Document Class: book 2014/09/29 v1.4h Standard LaTeX document class
(/usr/share/texmf-dist/tex/latex/base/bk12.clo))
Runaway argument?
width=5.5in,height=8.5in, \newcommand {\theversion }{1.0.4}
! Paragraph ended before \@fileswith@ptions was complete.
<to be read again>
\par
l.5
?
! Emergency stop.
<to be read again>
\par
l.5
No pages of output.
Transcript written on thinkdsp.image.log.
This is dvips(k) 5.997 Copyright 2017 Radical Eye Software (www.radicaleye.com)
dvips: DVI file can't be opened: thinkdsp.image.dvi: No such file or directory
hacha thinkdsp.html
cp up.png next.png back.png html
mv index.html thinkdsp.css thinkdsp*.html html
mv thinkdsp*.png *motif.gif html
mv: cannot stat 'thinkdsp*.png': No such file or directory
make: *** [Makefile:36: hevea] Error 1
As far as I can see, some command generates the thinkdsp.image.tex
file, which starts as follows:
\newif\ifimagen\imagentrue
\documentclass[12pt]{book}
\usepackage[width=5.5in,height=8.5in,
\newcommand{\theversion}{1.0.4}
\pagestyle{empty}
\thispagestyle{empty}
\begin{document}
\newtheorem{exercise}{Exercise}[chapter]
\setcounter{chapter}{-1}
The \usepackage[width=5.5in,height=8.5in,
command (line 3) looks wrong to me.
On line 316 of thinkdsp.py
, Spectrum.angles
is defined taking an argument i
, but this argument is never used. Consider removing?
I am using your notebooks with Jupyter 4.2.1 and Python 3.5.2 under Ubuntu Desktop 16.10
The code snippet
def Config(**options):
for name in names:
if name in options:
getattr(pyplot, name)(options[name])
generates TypeError: 'tuple' object is not callable
The following seems to fix it for me
def Config(**options):
for name in names:
if name in options:
try:
getattr(pyplot, name)(options[name])
except TypeError:
setattr(pyplot, name, options[name])
I can't get the wav files to playback when I run the code in spyder
Hi czn i get to know on how to create my link in github
Hi
I use the function wave.ts += 1 to shift my wav file in 1 second.
How can I do correlation check with my new wave (that start 1 second later)?
Best regrads,
Noam
the equation is written:
A exp{ i Phi0 } . exp{ i 2 pi f t } =A exp{ i 2 pi f t + Phi0 }
but it should:
A exp{ i Phi0 } . exp{ i 2 pi f t } =A exp{ i (2 pi f t + Phi0) }
The convolution theorem in Section 8.4 is not correct. Multiplication of the DFTs of two signals results in the periodic/circular convolution of the two signals. This is why zero-padding is required when one aims at the linear convolution in order to compute the output of a linear-time invariant system. You fill find this in amost any good textbook on DSP or since you also like Jupyter notebooks http://nbviewer.jupyter.org/github/spatialaudio/digital-signal-processing-lecture/blob/master/nonrecursive_filters/fast_convolution.ipynb.
From reading this paragraph: https://github.com/AllenDowney/ThinkDSP/blob/master/book/book.tex#L445, I believe that some confusion might arise for a newbie between cycle
and period
.
What about slightly rephrasing this paragraph to clarify what the definition of period
is?
On the output html page: http://greenteapress.com/thinkdsp/html/index.html, there is an "0pt" text before the title.
Could it come from here: https://github.com/AllenDowney/ThinkDSP/blob/master/book/book.tex#L71 ?
If yes, would an empty field {}
provide the same result (no indent)?
As the title reads, I have hit a bit of a snub trying the code out.
Trying to run the code:
import thinkdsp
wave = thinkdsp.read_wave('100475__iluppai__saxophone-weep.wav')
spectrogram = wave.make_spectrogram(seg_length=512)
spectrogram.plot(high=700)
on Python 3.6, resulting in:
Traceback (most recent call last):
...
File "/Users/filipthor/PycharmProjects/music/main.py", line 9, in
spectrogram = wave.make_spectrogram(seg_length=512)
File "/Users/filipthor/anaconda/lib/python3.6/site-packages/thinkdsp.py", line 943, in make_spectrogram
segment = self.slice(i, j)
File "/Users/filipthor/anaconda/lib/python3.6/site-packages/thinkdsp.py", line 897, in slice
ys = self.ys[i:j].copy()
TypeError: slice indices must be integers or None or have an index method
Any ideas?
I populated from your tree tonight (1/4/2016) and ran the Makefile in the code directory. I received the following error:
python: can't open file 'violin.py': [Errno 2] No such file or directory
violin.py is not in the code directory. Can you please check it in?
(Typos)
Page 66 (Chapter 6):
"So each element of y is the sum of four frequency component"
I suppose it should be written 'ys' and not 'y'
chap02soln - Markdown text right before "Exercise 3":
"TIf you compare it to this 500 Hz sine wave, you might hear what I mean."
Very minimal but I presume it is supposed to be written "If" and not "TIf".
Page 84 (Chapter 7):
"Instead, I will rewrite it to take just ys and compute freq and ts itself."
I think it should be written 'fs' and not 'freq'.
Page 86 (Chapter 7):
"The Spectrum class in thinkdsp is based on np.ftt.rfft"
The name of the module is "np.fft.rfft"
Page 90 (Chapter 8):
"padded is a version of the window with zeros added to the end so it has the same length as segment.ys prod is the product of the window and the wave array."
Based on previous chapters, I think it's missing a '\n' between 'segment.ys' and 'prod'
Page 91 (Chapter 8):
"rolled = np.roll(rolled, 1)"
It is supposed to be written 'padded' instead of 'rolled', right?
Page 95 (Chapter 8):
"And for any filter than can be expressed by element-wise multiplication in the frequency domain, there is a corresponding window."
I think you meant to write 'that' and not 'than'.
Page 99 (Chapter 8):
"To compute autocorrelation using convolution, have to zero-pad the signal to double the length."
It sounds like you meant to write "we have" instead of just 'have'.
Page 100 (Chapter 8):
"In addition to the Gaussian window we used in this window, create a Hamming window with the same size."
I think you meant to write "(...) we used in this chapter (...)".
Page 102 (Chapter 8):
"ys = df.close.values[::-1]
close = thinkdsp.Wave(ys, framerate=1)
spectrum = wave.make_spectrum()"
It should be "close.make_spectrum()" instead of "wave.make_spectrum()"
Page 116 (Chapter 10):
"(...) we can compute the impulse response by multiplying spectrum of the impulse and the filter, (...)"
This sentence seems to be missing a "the" before the word "spectrum".
Page 127 (Chapter 11):
"I’ll uses that to explain amplitude modulation (AM)"
I think "use" should be used in place of "uses"
Page 131 (chapter 11):
"“Sampling” is the process of measuring an analog signal at an series of points in time, usually with equal spacing"
I've always thought that we used 'a' if the following word started with a consonant sound and 'an' if it started with a vowel sound. Not sure if it is a mistake on "an series" but it definitely sounds like one to me.
Page 137 (chapter 11):
"Figure 11.7 (right) shows what this filter looks like. Of course, multiplication by the this filter, in the frequency domain, corresponds to convolution with a window in the time domain. We can find out what that window is by computing the inverse DFT of the filter, which is shown in Figure 11.7 (left)."
The 'right' and 'left' indicators are swapped as well as in the figure's caption.
(Miscellaneous)
chap02soln - Exercise 1, evaluate:
"cycles = self.freq * ts + self.offset / np.pi / 2"
But in the book, in the evaluate function of SquareSignal it is written:
"cycles = self.freq * ts + self.offset / PI2"
It might be less confusing for readers if both "cycles" variables are defined the same way, since they have the same values.
Page 73 (Chapter 6 - Exercises):
Exercise 6.2 mentions sparse arrays but the solution on chap06soln does not. I'm not sure if this was intended or not.
Page 84 (Chapter 7):
In the definition of 'idft' (in the book) shouldn't it receive 'amps' as an argument and then return 'ys' instead of receiving 'ys' and returning 'amps'?
thinkdsp.py:
The 'read_wave' function doesn't seem to work well for 24 bit depths (introduces crackling).
chap11.ipynb
You mention in chapter 8 (page 95) the boxcar filter as being the DFT of the boxcar window. However, in chap11.ipynb, you mention the boxcar filter as being the filter that looks like the boxcar window rather than the DFT of the boxcar window. I found this confusing that's why I'm mentioning it.
Page 131 (chapter 11):
"The result is quite close to the original wave, although about half of the power is lost after demodulating and filtering. But that’s not a problem in practice, because much more of the power is lost in transmitting and receiving the broadcast signal. We have to amplify the result anyway, another factor of 2 is not an issue."
I don't want to be annoying but every time I read this paragraph it always sounds like fallacious reasoning to me because I'm guessing that, even with the power losses of transmission, it would be an issue if the power lost after demodulating and filtering ruined the sound. So maybe the real reason why the power lost after demodulating and filtering is not a problem is that multiplying by a factor of 2 does not damage the sound quality in a significant way.
Hello Mr Allen, I first wanna thank you for this very incredible work that you are doing.
So I want to use your module with Pyzo, but I don't know how to install it, can you please help me?
Thanks!
I wonder any of you is working book with a real hardware. It will be good to adapt the book with some cheap DSP boards. Any ideas or links for a new version of the book with hands-on applications?
The doctring for the Wave
class might be incomplete since the ts
parameter is missing: https://github.com/AllenDowney/ThinkDSP/blob/master/code/thinkdsp.py#L596
The docstring states ys: a "wave array" which is a numpy array of floats.
(https://github.com/AllenDowney/ThinkDSP/blob/master/code/thinkdsp.py#L598), but the function read_wave
only supports integers: https://github.com/AllenDowney/ThinkDSP/blob/master/code/thinkdsp.py#L112
Hello!
So I want to preform a deconvolution of two signals to obtaion the RI of a romm, the input response being a sinesweep in the output the record of it, but I don't know how to do that using thinkdsp, can anyone help me with the code.
On page 42, the following 2 lines of code should be inverted (i.e. config before plot):
spectrum.plot_power(linewidth=1, alpha=0.5)
thinkplot.config(xscale='log', yscale='log')
In thinkplot.py, I had to add the following 2 lines at the end of def Pcolor(...:
if pcolor:
plt.pcolormesh(X, Y, Z, **options)
plt.show() #ADDED THIS OTHERWISE NOTHING SHOWS UP
if contour:
cs = plt.contour(X, Y, Z, **options)
plt.clabel(cs, inline=1, fontsize=10)
plt.show() # DITTO
Excellent book though, I'm learning a lot!
Hello
How do I install the thinkdsp module on computer. So I can call the functions.
Cheers
TypeError Traceback (most recent call last)
<ipython-input-19-4acaa3d0f7dd> in <module>
----> 1 spectrum = bartlett_method(wave, seg_length=seg_length, win_flag=False)
2 spectrum.hs[0] = 0
3 len(spectrum)
<ipython-input-18-0c3fecb9be50> in bartlett_method(wave, seg_length, win_flag)
14 # compute the root mean power (which is like an amplitude)
15 hs = np.sqrt(sum(psds) / len(psds))
---> 16 fs = spectrums[0].fs
17
18 # make a Spectrum with the mean amplitudes
TypeError: 'dict_values' object is not subscriptable
Hi Allen,
I am trying to use low_pass() from the book ThinkDSP and I find the subsequent call to the amps of the filtered specrum gives an error: AttributeError: 'NoneType' object has no attribute 'amps'
Does the low_pass function return a new version of the spectrum or do I have to recreate the spectrum via wave? Can you help please?
All I am trying to do is filter the spectrum and then use the amps data?
Cheers,
Andrew.
The function find_index
(https://github.com/AllenDowney/ThinkDSP/blob/master/code/thinkdsp.py#L143) does the same as numpy.searchsorted
(http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.searchsorted.html) but in a less efficient way.
I cannot find a reason to maintain find_index
. Do you?
Chapter three Spectrogram header.
in wave.make_spectogram I am running into this error:
TypeError: slice indices must be integers or None or have an index method
Looks like wave.split()
expects integer input, but the spectrogram has division which causes i and j to go to float.
because this line is division:
step = seg_length / 2
maybe
step = seg_length//2
??
Run the function with a recent numpy version
a spectrogram should appear
error:
TypeError: slice indices must be integers or None or have an index method
This can be easily fixed by changing line 937 from
step = seg_length // 2
to
step = int(seg_length // 2)
this is because in the new numpy version a slice index can't be a float, and step, which is a float as the result of a // operation, goes on to be a slice index
(I'm new to open source and didn't know how to create a pull request, so I wrote it here)
There could be a minor issue with the __init__
of the DCT
class: https://github.com/AllenDowney/ThinkDSP/blob/master/code/thinkdsp.py#L485
It is an exact duplication of the __init__
of the parent _SpectrumParent
class: https://github.com/AllenDowney/ThinkDSP/blob/master/code/thinkdsp.py#L156. You could delete it or, in alternative, call the parent initialization (if you prefer to make it more explicit).
The file is this: ThinkDSP/code/cacophony.ipynb
While in python2 the reduce function is already present in python3 you have to import it through functools.
By adding "from functools import reduce" at the beginning of the code, It will work for both py2 and py3
P.s. thanks for you books
I encountered import error in the chap01.ipynb, based on the bug tracer, fractions.gcd() is replaced by math.gcd().
I change 'fractions' to 'math' in thinkdsp.py line 19 , error gone.
Hi.
The question is about noise generation. More specifically, I would like to generate white/ pink noise that decays (exponentially) by given time parameters. Could anyone share some advice about it? Thanks a lot.
What is the intent of such a declared theorem style: https://github.com/AllenDowney/ThinkDSP/blob/master/book/latexonly#L91 ?
Is it a left over of some past implementation? It looks like that the theorem style in use is exercise
, never myex
.
A number of supporting latex files are not found in the repo making the make
command not usable (outside your personal machine at least). Would you mind committing them (e.g. latexonly.tex) to this repo?
On line 454 of trunk/book.tex
a cosine signal with {\tt offset=pi/2} is identical to a sine
should change to
a sine signal with {\tt offset=pi/2} is identical to a cosine
or
a cosine signal with {\tt offset=-pi/2} is identical to a sine
Kindly forgive me if this is not the correct way to report such issues
Tried to use it not so easy...
Why not add a requirements.txt file and make it compile as package?
Hi Allen,
I just printed the new version 1.1.0 of your ThinkDSP book. All is fine, except the references to Sections and Figures, which are marked as ??. Version 1.0.5 was fine in this regards.
I believe that the term "octave" should deserve a (simplified?) definition within in the text, since it is commonly used in DSP but not so intuitive for a newbie. For instance, in the part about harmonics: https://github.com/AllenDowney/ThinkDSP/blob/master/book/book.tex#L581
Furthermore, reading the wikipedia links: https://en.wikipedia.org/wiki/Scientific_pitch_notation and https://en.wikipedia.org/wiki/Interval_%28music%29 that you added to sections 1.1 and 1.2 might distract the reader from the main target that is, if I well understood, general DSP. What is the value added to the concept of harmonics from knowing that 2200 is approximately C$\sharp$7, which is a major third above A6
(https://github.com/AllenDowney/ThinkDSP/blob/master/book/book.tex#L589)?
I've just started the thinkdsp pdf reading, and it's a very good material, indeed. I'm on the first chapter, and facing an issue that I cannot understand yet.
When I run:
from IPython.display import Audio\ audio = Audio(data=wave.ys, rate=wave.framerate)\ audio
I get <IPython.lib.display.Audio object>, but no player.
I'm running the code in Spyder, with Anaconda.
Hello Allen
I am working on a project to create a low_pass filter on a microphone input for real time playback, I am using the low pass_filter function from thinkdsp to learn . How could some one add a microphone with realtime audio playback. To have the low pass filter play real time?
When I use an even number of frequency samples to convole 2 spectra, this seem to generate a frequency bin error. If I use an odd number of samples there is no error
Use case: AM modulated signal (like Chp 11 of ThinkDSP) - modulating signal is a 100 Hz SquareSignal (square wave) use low_pass to limit bandwidth to 1000 Hz (so you have spikes at 100, 300, 500...900 Hz). The carrier frequency is a CosSignal, frequency of 10000 Hz. I use a framerate of 100000 to sample both waves for 1 second and convert to (full) spectra, then convolve them together. I get sidebands as expected but at 10101, 10301, ... 10901 and not quite mirror imaged 9901, 9701...90101 Hz and then mirror imaged on the negative frequency side - so a frequency bin error of 1.
Fix seems to be to use an odd number of samples (or roll the convolved hs array by -1) - I used a framerate of 100001 for example. Not sure if this is a known issue with numpy fft.fftshift or not or should we expect this from theory? Anyway maybe warn the user in thinkdsp or perhaps pad the input spectra or the convolved spectrum somehow?
Hi all
i just read the chapter 1 and ready to code a little bit. after cloned the repo, i run a few codes and python give me this words:No handles with labels found to put in legend. i googled something, but no results.
here is my codes, my running env is anaconda 3.5.2
:
from __future__ import print_function, division
# %matplotlib inline
import thinkdsp
import thinkplot
import numpy as np
cos_sig = thinkdsp.CosSignal(freq=440, amp=10, offset=0)
sin_sig = thinkdsp.SinSignal(freq=880, amp=0.5, offset=0)
cos_sig.plot()
thinkplot.config(xlabel='Time (s)', ylabel='amp(p)')
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.