<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def overlap_add(blk_sig, size=None, hop=None, wnd=None, normalize=True):
 """ Overlap-add algorithm using Numpy arrays. Parameters blk_sig : An iterable of blocks (sequences), such as the ``Stream.blocks`` result. size : Block size for each ``blk_sig`` element, in samples. hop : Number of samples for two adjacent blocks (defaults to the size). wnd : Windowing function to be applied to each block or any iterable with exactly ``size`` elements. If ``None`` (default), applies a rectangular window. normalize : Flag whether the window should be normalized so that the process could happen in the [-1; 1] range, dividing the window by its hop gain. Default is ``True``. Returns ------- A Stream instance with the blocks overlapped and added. See Also -------- Stream.blocks : Splits the Stream instance into blocks with given size and hop. blocks : Same to Stream.blocks but for without using the Stream class. chain : Lazily joins all iterables given as parameters. chain.from_iterable : Same to ``chain(*data)``, but the ``data`` evaluation is lazy. window : Window/apodization/tapering functions for a given size as a StrategyDict. Note ---- Each block has the window function applied to it and the result is the sum of the blocks without any edge-case special treatment for the first and last few blocks. """

  import numpy as np

  # Finds the size from data, if needed
  if size is None:
    blk_sig = Stream(blk_sig)
    size = len(blk_sig.peek())
  if hop is None:
    hop = size

  # Find the right windowing function to be applied
  if wnd is None:
    wnd = np.ones(size)
  elif callable(wnd) and not isinstance(wnd, Stream):
    wnd = wnd(size)
  if isinstance(wnd, Sequence):
    wnd = np.array(wnd)
  elif isinstance(wnd, Iterable):
    wnd = np.hstack(wnd)
  else:
    raise TypeError("Window should be an iterable or a callable")

  # Normalization to the [-1; 1] range
  if normalize:
    steps = Stream(wnd).blocks(hop).map(np.array)
    gain = np.sum(np.abs(np.vstack(steps)), 0).max()
    if gain: # If gain is zero, normalization couldn't have any effect
      wnd = wnd / gain # Can't use "/=" nor "*=" as Numpy would keep datatype

  # Overlap-add algorithm
  old = np.zeros(size)
  for blk in (wnd * blk for blk in blk_sig):
    blk[:-hop] += old[hop:]
    for el in blk[:hop]:
      yield el
    old = blk
  for el in old[hop:]: # No more blocks, finish yielding the last one
    yield el