{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Assignment 3, Direction 1 (Creative): Cover a song\n",
    "\n",
    "Authored by `[your AndrewID]` for 15-322/15-622 at Carnegie Mellon University.\n",
    "\n",
    "This starter code is a **suggestion**, not a requirement. You are free to\n",
    "restructure it however you like, as long as your submission satisfies the\n",
    "requirements in the assignment description."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import pyquist as pq\n",
    "from pyquist.helper import db_to_amplitude, pitch_to_frequency\n",
    "from pyquist.web.theorytab import fetch_theorytab\n",
    "\n",
    "# Find a song at https://www.hooktheory.com/theorytab, right click \"Open in\n",
    "# Hookpad\", and copy the link address.\n",
    "FAV_SONG = \"https://hookpad.hooktheory.com/?idOfSong=kwxAAAEdYxK\"\n",
    "\n",
    "# NOTE: Event times are in *beats*, not seconds.\n",
    "# NOTE: With harmony_as_notes=False, harmony events carry\n",
    "#       {\"duration\", \"root\", \"intervals\", \"inversion\"} instead of a \"pitch\".\n",
    "metronome, melody, harmony = fetch_theorytab(FAV_SONG, harmony_as_notes=False)\n",
    "\n",
    "print(f\"{60 * metronome.ticks_per_second:.1f} BPM\")\n",
    "print(\"-\" * 80)\n",
    "for e in melody[:4]:\n",
    "    print(e)\n",
    "print(\"-\" * 80)\n",
    "for e in harmony[:4]:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def arrange_score(melody: pq.Score, harmony: pq.Score) -> list[pq.Score]:\n",
    "    \"\"\"Returns one pq.Score per instrument part.\"\"\"\n",
    "    # This is where you do your symbolic arrangement work.\n",
    "    raise NotImplementedError(\"Arrange your parts here!\")\n",
    "\n",
    "\n",
    "parts = arrange_score(melody, harmony)\n",
    "assert len(parts) >= 3, \"You need at least 3 parts.\"\n",
    "assert all(isinstance(part, pq.Score) for part in parts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Each instrument is called as `instrument(**event.kwargs)`, so its parameters\n",
    "# must line up with the kwargs of the events in its part.\n",
    "def instrument_0(pitch: int, duration: float, sample_rate: int = 44100, **kwargs) -> pq.Audio:\n",
    "    # This is where you do your synthesis work.\n",
    "    raise NotImplementedError(\"Implement your first instrument here!\")\n",
    "\n",
    "\n",
    "def instrument_1(pitch: int, duration: float, sample_rate: int = 44100, **kwargs) -> pq.Audio:\n",
    "    raise NotImplementedError(\"Implement your second instrument here!\")\n",
    "\n",
    "\n",
    "def instrument_2(pitch: int, duration: float, sample_rate: int = 44100, **kwargs) -> pq.Audio:\n",
    "    raise NotImplementedError(\"Implement your third instrument here!\")\n",
    "\n",
    "\n",
    "instruments = [instrument_0, instrument_1, instrument_2]\n",
    "assert len(instruments) == len(parts), \"One instrument per part.\"\n",
    "\n",
    "# Passing `metronome` is what converts event times from beats to seconds.\n",
    "stems = [part.render(instrument, metronome=metronome) for part, instrument in zip(parts, instruments)]\n",
    "for i, stem in enumerate(stems):\n",
    "    stem.write(f\"stem_{i}.wav\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mix_stems(stems: list[pq.Audio]) -> pq.Audio:\n",
    "    \"\"\"Returns a single pq.Audio mixing all stems together.\"\"\"\n",
    "    # NOTE: Stems will generally have different lengths.\n",
    "    raise NotImplementedError(\"Mix your stems here!\")\n",
    "\n",
    "\n",
    "composition = mix_stems(stems)\n",
    "assert 45.0 <= composition.duration <= 120.0, (\n",
    "    f\"Composition is {composition.duration:.1f}s; must be 45-120s.\"\n",
    ")\n",
    "\n",
    "composition.write(\"composition.wav\")\n",
    "pq.play(composition)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
