Commit aaaabe5c authored by Nadir Dalla Pozza's avatar Nadir Dalla Pozza
Browse files

Working code

parent 7d09f0fc
# Tape Audio Restoration
# Packager
## Description
Implements the Technical Specification of [MPAI CAE-ARP](https://mpai.community/standards/mpai-cae/about-mpai-cae/#Figure2) *Tape Audio Restoration* AIM, providing:
* Restored Audio Files;
* Editing List.
## Getting started
The *Tape Audio Restoration* is written in Python 3.9 which is therefore required to run the program.
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
## Installation
[PyYaml](https://pyyaml.org) is required for reading the configuration file. You can install it with:
```
cd existing_repo
git remote add origin https://gitlab.dei.unipd.it/mpai/tape-audio-restoration.git
git branch -M main
git push -uf origin main
pip install pyyaml
```
[NumPy](https://numpy.org) and [SciPy](https://scipy.org) are required for correcting the audio of the Preservation Audio File. You can install them with:
```
pip install numpy
pip install scipy
```
Finally, [Matplotlib](https://matplotlib.org) is required for (optionally) plotting the frequency responses of the filters. You can install it with:
```
pip install matplotlib
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.dei.unipd.it/mpai/tape-audio-restoration/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
You can also use `requirements.txt` file to install all needed dependencies at once:
```
pip install -r requirements.txt
```
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Usage
Once the libraries are installed, you should customise the configuration file `config.yaml`.
There are nine required parameters:
1. `WORKING_PATH` that specifies the working path where all input files are stored and where all output files will be saved;
2. `PRESERVATION_FILE_NAME` that specifies the name of the Preservation Audio File to be considered. Use an empty string
to plot filter frequency response;
3. `STANDARD_W` that specifies the equalisation standard used to record the tape;
4. `SPEED_W` that specifies the speed used when recording the tape;
5. `STANDARD_R` that specifies the equalisation standard used to read the tape;
6. `SPEED_R` that specifies the speed used when reading the tape;
7. `FS` that specifies the sampling frequency to be used for the filters and the impulse responses.
This parameter won't be considered if `PRESERVATION_FILE_NAME` parameter is not an empty string;
8. `BPS` that specifies the bits-per-sample to be used when saving the filter impulse response as WAV;
9. `PLOTS` that specifies if the plots of the frequency response should be shown.
To execute the script without issues, the inner structure of the `WORKING_PATH` directory shall be like:
```
.
├── AccessCopyFiles
│ └── ...
├── PreservationAudioFile
│ ├── File1.wav
│ ├── File2.wav
│ └── ...
├── PreservationAudioVisualFile
│ ├── File1.mp4
│ ├── File2.mp4
│ └── ...
├── PreservationMasterFiles
│ └── ...
└── temp
├── File1
│ ├── AudioAnalyser_IrregullarityFileOutput1.json
│ ├── AudioAnalyser_IrregullarityFileOutput2.json
│ ├── AudioBlocks
│ │ ├── AudioBlock1.jpg
│ │ ├── AudioBlock2.jpg
│ │ └── ...
│ ├── EditingList.json
│ ├── IrregularityImages
│ │ ├── IrregularityImage1.jpg
│ │ ├── IrregularityImage2.jpg
│ │ └── ...
│ ├── RestoredAudioFiles
│ │ ├── RestoredAudioFile1.wav
│ │ ├── RestoredAudioFile2.wav
│ │ └── ...
│ ├── TapeIrregularityClassifier_IrregularityFileOutput1.json
│ ├── TapeIrregularityClassifier_IrregularityFileOutput2.json
│ ├── VideoAnalyser_IrregularityFileOutput1.json
│ └── VideoAnalyser_IrregularityFileOutput2.json
├── File2
│ ├── AudioAnalyser_IrregullarityFileOutput1.json
│ ├── AudioAnalyser_IrregullarityFileOutput2.json
│ ├── AudioBlocks
│ │ ├── AudioBlock1.jpg
│ │ ├── AudioBlock2.jpg
│ │ └── ...
│ ├── EditingList.json
│ ├── IrregularityImages
│ │ ├── IrregularityImage1.jpg
│ │ ├── IrregularityImage2.jpg
│ │ └── ...
│ ├── RestoredAudioFiles
│ │ ├── RestoredAudioFile1.wav
│ │ ├── RestoredAudioFile2.wav
│ │ └── ...
│ ├── TapeIrregularityClassifier_IrregularityFileOutput1.json
│ ├── TapeIrregularityClassifier_IrregularityFileOutput2.json
│ ├── VideoAnalyser_IrregularityFileOutput1.json
│ └── VideoAnalyser_IrregularityFileOutput2.json
└── ...
```
`PreservationAudioFile` and `PreservationAudioVisualFile` directories contain the input of ARP Workflow, while `AccessCopyFiles` and `PreservationMasterFiles` directories contain its output. `temp` directory is used to store all files exchanged between the AIMs within the Workflow.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
Please note that:
* Corresponding input files shall present the same name;
* The name of Irregularity Files given above is ***mandatory***.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
With this structure, `PRESERVATION_FILE_NAME` parameter could be equal to `File1` or `File2`.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
You can now launch the *Tape Audio Restoration* from the command line with:
```
python3 main.py
```
Useful log information will be displayed during execution, requiring occasional interaction.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
If you require additional information or have any problem, you can contact us at:
* Nadir Dalla Pozza (nadir.dallapozza@unipd.it);
* Niccolò Pretto (niccolo.pretto@unipd.it).
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
## Authors and acknowledgment
This project was developed by:
* Nadir Dalla Pozza (University of Padova);
* Niccolò Pretto (University of Padova);
* Sergio Canazza (University of Padova).
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
This project takes advantage of the following libraries:
* [Matplotlib](https://matplotlib.org);
* [NumPy](https://numpy.org);
* [PyYaml](https://pyyaml.org);
* [SciPy](https://scipy.org).
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
Developed with Python IDE [PyCharm Community](https://www.jetbrains.com/pycharm/).
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
This project is licensed with [GNU GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.html).
# Working path
WORKING_PATH: "/Users/nadir/Documents/MPAI-CAE/Workflow"
# Name of the Preservation Audio File (without extension)
# Use an empty string to plot filters frequency response
PRESERVATION_FILE_NAME: "sample12_W7N_R15C"
# PRESERVATION_FILE_NAME: "BornToDie"
# PRESERVATION_FILE_NAME: ""
# Equalisation standard used to record the tape.
# Accepted values: ("NAB", "CCIR")
STANDARD_W: "NAB"
# Recording tape speed [ips].
# Accepted values: (3.75, 7.5, 15, 30)
SPEED_W: 7.5
# Equalization standard used to read the tape.
# Accepted values: ("NAB", "CCIR")
STANDARD_R: "CCIR"
# Reading tape speed [ips].
# Accepted values: (3.75, 7.5, 15, 30)
SPEED_R: 15
# Sampling frequency to be used for filters and impulse response [Hz].
# Accepted values: (44100, 48000, 96000)
# NOTE: Overwritten with input file one, if any.
FS: 96000
# Bits per sample to be used when saving the filter impulse response audio file.
# Accepted values: (8, 16, 24, 32)
# NOTE_1: Low values (8, 16) may introduce oscillations when exporting the filter impulse response in .wav format.
# NOTE_2: Overwritten with input file one, if any.
BPS: 24
# Enable plotting filter frequency responses
# Accepted values: (true, false)
PLOTS: false
\ No newline at end of file
#!/usr/bin/env python
"""
MPAI CAE-ARP Tape Audio Restoration.
Implements MPAI CAE-ARP Tape Audio Restoration Technical Specification.
It identifies and restore portions of the Preservation Audio File, providing:
- Restored Audio Files;
- Editing List
"""
import matplotlib.pyplot as plt
import numpy as np
import os
import shutil
import yaml
from control import c2d, TransferFunction
from scipy.io import wavfile
from scipy.signal import freqs, freqz, tf2zpk, zpk2tf, lfilter
__author__ = "Nadir Dalla Pozza"
__copyright__ = "Copyright 2022, Audio Innova S.r.l."
__credits__ = ["Niccolò Pretto", "Nadir Dalla Pozza", "Sergio Canazza"]
__license__ = "GPL v3.0"
__version__ = "1.0.1"
__maintainer__ = "Nadir Dalla Pozza"
__email__ = "nadir.dallapozza@unipd.it"
__status__ = "Production"
# Class for customizing Console Colors
class CC:
PURPLE = '\033[95m'
CYAN = '\033[96m'
DARK_CYAN = '\033[36m'
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
END = '\033[0m'
def save_file(file):
if not os.path.exists(temp_path):
# Create directory
os.mkdir(temp_path)
print("temp directory '% s' created" % temp_path)
raf_path = os.path.join(temp_path, 'RestoredAudioFiles')
make_raf = False
if not os.path.exists(raf_path):
# Create directory
os.mkdir(raf_path)
make_raf = True
print("Restored Audio Files directory '% s' created" % raf_path)
else:
print((CC.PURPLE + "Restored Audio Files directory '% s' already exists!" + CC.END) % raf_path)
overwrite = input('Do you want to overwrite it? [y/n]: ')
if overwrite.casefold() == 'y':
# Overwrite directory
shutil.rmtree(raf_path)
os.mkdir(raf_path)
make_raf = True
print('Restored Audio Files directory overwritten')
elif overwrite.casefold() != 'n':
print(CC.RED + 'Unknown command, exiting' + CC.END)
quit(os.EX_USAGE)
if make_raf:
print("Saving Restored Audio File to: '%s' ..." % raf_path)
wavfile.write(os.path.join(raf_path, '1.wav'), FS, file)
if __name__ == '__main__':
# Read configuration file
config = object
try:
config = yaml.safe_load(open('config.yaml', 'r'))
if 'WORKING_PATH' not in config:
print(CC.RED + 'WORKING_PATH key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
if 'PRESERVATION_FILE_NAME' not in config:
print(CC.RED + 'PRESERVATION_FILE_NAME key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
if 'STANDARD_W' not in config:
print(CC.RED + 'STANDARD_W key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
if 'SPEED_W' not in config:
print(CC.RED + 'SPEED_W key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
if 'STANDARD_R' not in config:
print(CC.RED + 'STANDARD_R key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
if 'SPEED_R' not in config:
print(CC.RED + 'SPEED_R key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
if 'FS' not in config:
print(CC.RED + 'FS key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
if 'BPS' not in config:
print(CC.RED + 'BPS key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
if 'PLOTS' not in config:
print(CC.RED + 'PLOTS key not found in config.yaml!' + CC.END)
quit(os.EX_CONFIG)
except FileNotFoundError:
print(CC.RED + 'config.yaml file not found!' + CC.END)
quit(os.EX_NOINPUT)
# config.yaml variables
WORKING_PATH = config['WORKING_PATH']
PRESERVATION_FILE_NAME = config['PRESERVATION_FILE_NAME']
STANDARD_W = config['STANDARD_W']
SPEED_W = config['SPEED_W']
STANDARD_R = config['STANDARD_R']
SPEED_R = config['SPEED_R']
FS = config['FS']
BPS = config['BPS']
PLOTS = config['PLOTS']
# Output path
temp_path = os.path.join(WORKING_PATH, 'temp', PRESERVATION_FILE_NAME)
# Configuration parameters check
# Recording tape speed check
if SPEED_W != 3.75 and SPEED_W != 7.5 and SPEED_W != 15 and SPEED_W != 30:
print(
CC.RED + 'Incorrect SPEED_W: \'' + str(SPEED_W) + '\'. Accepted value are: 3.75, 7.5, 15, 30.' + CC.END
)
quit(os.EX_CONFIG)
# Reading tape speed check.
if SPEED_R != 3.75 and SPEED_R != 7.5 and SPEED_R != 15 and SPEED_R != 30:
print(
CC.RED + 'Incorrect SPEED_R: \'' + str(SPEED_R) + '\'. Accepted value are: 3.75, 7.5, 15, 30.' + CC.END
)
quit(os.EX_CONFIG)
# Equalization standard check.
if not (STANDARD_R == 'CCIR' or STANDARD_R == 'NAB'):
print(
CC.RED + 'Incorrect STANDARD_R: \'' + STANDARD_R + '\'. Accepted values are: CCIR, NAB.' + CC.END
)
quit(os.EX_CONFIG)
if not (STANDARD_W == 'CCIR' or STANDARD_W == 'NAB'):
print(
CC.RED + 'Incorrect STANDARD_W: \'' + STANDARD_W + '\'. Accepted values are: CCIR, NAB.' + CC.END
)
quit(os.EX_CONFIG)
# CCIR speed check.
if STANDARD_W == 'CCIR' and SPEED_W == 3.75:
print(
CC.YELLOW + 'CCIR is undefined at 3.75 ips. Recording equalization standard is set to NAB.' + CC.END
)
STANDARD_W = 'NAB'
if STANDARD_R == 'CCIR' and SPEED_R == 3.75:
print(
CC.YELLOW + 'CCIR is undefined at 3.75 ips. Reading equalization standard is set to NAB.' + CC.END
)
STANDARD_R = 'NAB'
# NAB speed check.
if STANDARD_W == 'NAB' and SPEED_W == 30:
print(
CC.YELLOW + 'NAB is undefined at 30 ips. Recording equalization standard is set to CCIR.' + CC.END
)
STANDARD_W = 'CCIR'
if STANDARD_R == 'NAB' and SPEED_R == 30:
print(
CC.YELLOW + 'NAB is undefined at 30 ips. Reading equalization standard is set to CCIR.' + CC.END
)
STANDARD_R = 'CCIR'
# Sampling frequency check.
if len(PRESERVATION_FILE_NAME) == 0:
if FS != 44100 and FS != 48000 and FS != 96000:
print(
CC.RED + 'Incorrect FS: \'' + str(FS) + '\'. Accepted values are: 44100, 48000, 96000.' + CC.END
)
quit(os.EX_CONFIG)
# Bits per sample check.
if BPS != 8 and BPS != 16 and BPS != 24 and BPS != 32:
print(
CC.RED + 'Incorrect BPS: \'' + str(BPS) + '\'. Accepted values are: 8, 16, 24, 32.' + CC.END
)
quit(os.EX_CONFIG)
# Display input parameters
print('Input parameters:')
print(' WORKING_PATH: ' + WORKING_PATH)
print(' PRESERVATION_FILE_NAME: ' + PRESERVATION_FILE_NAME)
print(' STANDARD_W: ' + STANDARD_W)
print(' SPEED_W: ' + str(SPEED_W) + ' ips')
print(' STANDARD_R: ' + STANDARD_R)
print(' SPEED_R: ' + str(SPEED_R) + ' ips')
if len(PRESERVATION_FILE_NAME) == 0:
print(' FS: ' + str(FS) + ' Hz')
print(' BPS: ' + str(BPS) + '\n')
# Preservation Audio File check
paf = []
if len(PRESERVATION_FILE_NAME) > 0:
audio_file = PRESERVATION_FILE_NAME + '.wav'
paf_path = os.path.join(WORKING_PATH, 'PreservationAudioFile', audio_file)
try:
print("Opening '%s'..." % paf_path)
FS, paf = wavfile.read(paf_path)
print('Preservation Audio File opened!')
print('Overwritten parameters:')
print(' FS: ' + str(FS) + ' Hz\n')
except OSError:
print(CC.RED + "Preservation Audio File not found!" + CC.END)
quit(os.EX_NOINPUT)
# Equalization standard time constants
# CCIR time constants.
t2_30 = 17.5 * 10**(-6) # time constant CCIR_30
t2_15 = 35 * 10**(-6) # time constant CCIR_15
t2_7 = 70 * 10**(-6) # time constant CCIR_7.5
# NAB time constants.
t3 = 3180 * 10**(-6)
t4_15 = 50 * 10**(-6) # time constant NAB_15
t4_7 = 50 * 10**(-6) # time constant NAB_7.5
t4_3 = 90 * 10**(-6) # time constant NAB_3.75
# Decision stage
a = [] # Filter numerator
b = [] # Filter denominator
case = 0 # Reference case number
# This section will establish which time constants must be modified to obtain the desired equalisation standard.
if STANDARD_W == 'CCIR':
if SPEED_W == 30:
if STANDARD_R == 'NAB':
# Case 1
if SPEED_R == 15:
FS = 2 * FS # Doubling the sampling frequency
# Correction filter: NABw15_mod + CCIRr30
# - NAB constants divided by 2
t3 = t3 / 2
t4 = t4_15 / 2
# - CCIR_30 constant not altered
t2 = t2_30
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 1
# Case 2
elif SPEED_R == 7.5:
FS = 4 * FS # Quadrupling the sampling frequency
# Correction filter: NABw7.5_mod + CCIRr30
# - NAB constants divided by 4
t3 = t3 / 4
t4 = t4_7 / 4
# - CCIR_30 constant not altered
t2 = t2_30
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 2
# Case 3
else: # SPEED_R == 3.75
FS = 8 * FS # Multiplying by 8 the sampling frequency
# Correction filter: NABw3.75_mod + CCIRr30
# - NAB constants divided by 8
t3 = t3 / 8
t4 = t4_3 / 8
# - CCIR_30 constant not altered
t2 = t2_30
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 3
else: # STANDARD_R == 'CCIR'
# Case 31
if SPEED_R == 30:
print('Reference case: 31')
print(CC.GREEN + 'Nothing to do!' + CC.END)
quit(os.EX_OK)
# Case 15
elif SPEED_R == 15:
FS = 2 * FS # Doubling sampling frequency
# Plot information
case = 15
# Case 16
else: # SPEED_R == 7.5
FS = 4 * FS # Quadrupling the sampling frequency
# Plot information
case = 16
elif SPEED_W == 15:
if STANDARD_R == 'NAB':
# Case 28
if SPEED_R == 15:
# No speed change
# Correction filter: NABw15 + CCIRr15
# - NAB_15 constants not altered
t4 = t4_15
# - CCIR_30 constant not altered
t2 = t2_15
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 28
# Case 6
elif SPEED_R == 7.5:
FS = 2 * FS # Doubling the sampling frequency
# Correction filter: NABw7.5_mod + CCIRr15
# - NAB constants divided by 2
t3 = t3 / 2
t4 = t4_7 / 2
# - CCIR_15 constant not altered
t2 = t2_15
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 6
# Case 7
else: # SPEED_R == 3.75
FS = 4 * FS # Quadrupling the sampling frequency
# Correction filter: NABw3.75_mod + CCIRr15
# - NAB constants divided by 4
t3 = t3 / 4
t4 = t4_3 / 4
# - CCIR_15 constant not altered
t2 = t2_15
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 7
else: # STANDARD_R == 'CCIR'
# Case 19
if SPEED_R == 30:
FS = FS / 2 # Halving the sampling frequency
# Plot information
case = 19
# Case 33
elif SPEED_R == 15:
print('Reference case: 33')
print(CC.GREEN + 'Nothing to do!' + CC.END)
quit(os.EX_OK)
# Case 20
else: # SPEED_R == 7.5
FS = 2 * FS # Doubling the sampling frequency
# Plot information
case = 20
else: # SPEED_W == 7.5
if STANDARD_R == 'NAB':
# Case 10
if SPEED_R == 15:
FS = FS / 2 # Halving the sampling frequency
# Correction filter: NABw15_mod + CCIRr7.5
# - NAB constants multiplied by 2
t3 = t3 * 2
t4 = t4_15 * 2
# - CCIR_7.5 constant not altered
t2 = t2_7
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 10
# Case 30
elif SPEED_R == 7.5:
# No speed change
# Correction filter: NABw7.5 + CCIRr7.5
# - NAB_7.5 constant not altered
t4 = t4_7
# - CCIR_7.5 constant not altered
t2 = t2_7
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 30
# Case 11
else: # SPEED_R == 3.75
FS = 2 * FS # Doubling the sampling frequency
# Correction filter: NABw3.75_mod + CCIRr7.5
# - NAB constants divided by 2
t3 = t3 / 2
t4 = t4_3 / 2
# - CCIR_7.5 constant not altered
t2 = t2_7
# Filter coefficients
a = [t2 * t3, t2 + t3, 1]
b = [t3 * t4, t3, 0]
# Plot information
case = 11
else: # STANDARD_R == 'CCIR'
# Case 23
if SPEED_R == 30:
FS = FS / 4 # Quartering the sampling frequency
# Plot information
case = 23
# Case 24
elif SPEED_R == 15:
FS = FS / 2 # Halving the sampling frequency
# Plot information
case = 24
# Case 35
else: # SPEED_R == 7.5
print('Reference case: 35')
print(CC.GREEN + 'Nothing to do!' + CC.END)
quit(os.EX_OK)
else: # STANDARD_W == 'NAB'
if SPEED_W == 15:
if STANDARD_R == 'NAB':
# Case 32
if SPEED_R == 15:
print('Reference case: 32')
print(CC.GREEN + 'Nothing to do!' + CC.END)
quit(os.EX_OK)
# Case 17
elif SPEED_R == 7.5:
FS = 2 * FS # Doubling the sampling frequency
# Correction filter: NABw7.5_mod + NABr15
# - NABw constants divided by 2
t3_mod = t3 / 2
t4_mod = t4_7 / 2
# - NABr constant not altered
t4 = t4_15
# Filter coefficients
a = [t3 * t3_mod * t4, t3 * (t3_mod + t4), t3]
b = [t3 * t3_mod * t4_mod, t3_mod * (t3 + t4_mod), t3_mod]
# Plot information
case = 17
# Case 18
else: # SPEED_R == 3.75
FS = 4 * FS # Quadrupling the sampling frequency
# Correction filter: NABw3.75_mod + NABr15
# - NAB constants divided by 4
t3_mod = t3 / 4
t4_mod = t4_3 / 4
# - NABr constant not altered
t4 = t4_15
# Filter coefficients
a = [t3 * t3_mod * t4, t3 * (t3_mod + t4), t3]
b = [t3 * t3_mod * t4_mod, t3_mod * (t3 + t4_mod), t3_mod]
# Plot information
case = 18
else: # STANDARD_R == 'CCIR'
# Case 4
if SPEED_R == 30:
FS = FS / 2 # Halving the sampling frequency
# Correction filter: CCIRw30_mod + NABr15
# - CCIR_30 constant multiplied by 2
t2 = t2_30 * 2
# - NAB_15 constant not altered
t4 = t4_15
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 4
# Case 27
elif SPEED_R == 15:
# No speed change
# Correction filter: CCIRw15 + NABr15
# - CCIR_15 constant not altered
t2 = t2_15
# - NAB_15 constant not altered
t4 = t4_15
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 27
# Case 5
else: # SPEED_R == 7.5
FS = FS * 2 # Doubling the sampling frequency
# Correction filter: CCIRw7.5_mod + NABr15
# - CCIR_7.5 constant divided by 2
t2 = t2_7 / 2
# - NAB_15 constant not altered
t4 = t4_15
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 5
elif SPEED_W == 7.5:
if STANDARD_R == 'NAB':
# Case 21
if SPEED_R == 15:
FS = FS / 2 # Halving the sampling frequency
# Correction filter: NABw15_mod + NABr7.5
# - NABw constants multiplied by 2
t3_mod = t3 * 2
t4_mod = t4_15 * 2
# - NABr constant not altered
t4 = t4_7
# Filter coefficients
a = [t3 * t3_mod * t4, t3 * (t3_mod + t4), t3]
b = [t3 * t3_mod * t4_mod, t3_mod * (t3 + t4_mod), t3_mod]
# Plot information
case = 21
# Case 34
elif SPEED_R == 7.5:
print('Reference case: 34')
print(CC.GREEN + 'Nothing to do!' + CC.END)
quit(os.EX_OK)
# Case 22
else: # SPEED_R == 3.75
FS = 2 * FS # Doubling the sampling frequency
# Correction filter: NABw3.75_mod + NABr7.5
# - NABw constants divided by 2
t3_mod = t3 / 2
t4_mod = t4_3 / 2
# - NABr constant not altered
t4 = t4_7
# Filter coefficients
a = [t3 * t3_mod * t4, t3 * (t3_mod + t4), t3]
b = [t3 * t3_mod * t4_mod, t3_mod * (t3 + t4_mod), t3_mod]
# Plot information
case = 22
else: # STANDARD_R == 'CCIR'
# Case 8
if SPEED_R == 30:
FS = FS / 4 # Quartering the sampling frequency
# Correction filter: CCIRw30_mod + NABr7.5
# - CCIR_30 constant multiplied by 4
t2 = t2_30 * 4
# - NAB_7.5 constant not altered
t4 = t4_7
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 8
# Case 9
elif SPEED_R == 15:
FS = FS / 2 # Halving the sampling frequency
# Correction filter: CCIRw15_mod + NABr7.5
# - CCIR_15 constant multiplied by 2
t2 = t2_15 * 2
# - NAB_7.5 constant not altered
t4 = t4_7
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 9
# Case 29
else: # SPEED_R == 7.5
# No speed change
# Correction filter: CCIRw7.5 + NABr7.5
# - CCIR_7.5 constant not altered
t2 = t2_7
# - NAB_7.5 constant not altered
t4 = t4_7
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 29
else: # SPEED_W == 3.75
if STANDARD_R == 'NAB':
# Case 25
if SPEED_R == 15:
FS = FS / 4 # Quartering the sampling frequency
# Correction filter: NABw15_mod + NABr3.75
# - NAB constants multiplied by 4
t3_mod = t3 * 4
t4_mod = t4_15 * 4
# - NABr constant not altered
t4 = t4_3
# Filter coefficients
a = [t3 * t3_mod * t4, t3 * (t3_mod + t4), t3]
b = [t3 * t3_mod * t4_mod, t3_mod * (t3 + t4_mod), t3_mod]
# Plot information
case = 25
# Case 26
elif SPEED_R == 7.5:
FS = FS / 2 # Halving the sampling frequency
# Correction filter: NABw7.5_mod + NABr3.75
# - NAB constants multiplied by 2
t3_mod = t3 * 2
t4_mod = t4_7 * 2
# - NABr constant not altered
t4 = t4_3
# Filter coefficients
a = [t3 * t3_mod * t4, t3 * (t3_mod + t4), t3]
b = [t3 * t3_mod * t4_mod, t3_mod * (t3 + t4_mod), t3_mod]
# Plot information
case = 26
# Case 36
else: # SPEED_R == 3.75
print('Reference case: 36')
print(CC.GREEN + 'Nothing to do!' + CC.END)
quit(os.EX_OK)
else: # STANDARD_R == 'CCIR'
# Case 12
if SPEED_R == 30:
FS = FS / 8 # Dividing by 8 the sampling frequency
# Correction filter: CCIRw30_mod + NABr3.75
# - CCIR_30 constant multiplied by 8
t2 = t2_30 * 8
# - NAB_3.75 constant not altered
t4 = t4_3
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 12
# Case 13
elif SPEED_R == 15:
FS = FS / 4 # Quartering the sampling frequency
# Correction filter: CCIRw15_mod + NABr3.75
# - CCIR_15 constant multiplied by 4
t2 = t2_15 * 4
# - NAB_3.75 constant not altered
t4 = t4_3
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 13
# Case 14
else: # SPEED_R == 7.5
FS = FS / 2 # Halving the sampling frequency
# Correction filter: CCIRw7.5_mod + NABr3.75
# - CCIR_7.5 constant multiplied by 2
t2 = t2_7 * 2
# - NAB_3.75 constant not altered
t4 = t4_3
# Filter coefficients
a = [t3 * t4, t3, 0]
b = [t2 * t3, t2 + t3, 1]
# Plot information
case = 14
# Casting FS to int because wavfile.write() is stupid
FS = round(FS)
print('Reference case: ' + str(case))
print('Operational sampling frequency: ' + str(FS) + ' Hz.')
# Correction filter
# Not all cases present a correction filter!
if len(a) != 0:
# Analog transfer function
H_a = TransferFunction(a, b)
# Analog frequency vector
w_a = np.logspace(np.log10(1), np.log10(FS * np.pi), 5000)
if PLOTS:
# Analog filter frequency response
w_t, h_t = freqs(a, b, worN=w_a)
# Plot analog graph
# - Magnitude
plt.subplot(2, 1, 1)
plt.semilogx(w_t / (2 * np.pi), 20 * np.log10(abs(h_t)))
plt.xlim([1, 24000])
plt.xlabel('Frequency')
plt.ylim([-40, 40])
plt.ylabel('Amplitude response [dB]')
plt.grid(True)
# - Phase
plt.subplot(2, 1, 2)
plt.semilogx(w_t / (2 * np.pi), np.angle(h_t) * 180 / np.pi)
plt.xlim([1, 24000])
plt.xlabel('Frequency')
plt.ylabel('Phase [deg]')
plt.grid(True)
# Digital transfer function through bilinear digitisation
H_d = c2d(H_a, 1/FS, 'bilinear')
num_d = H_d.num[0][0] # Inspect Hd.num to see why [0][0] is needed...
den_d = H_d.den[0][0] # Same story here
# Digital frequency vector
w_d = np.logspace(np.log10(1), np.log10(FS / 2), 5000)
if PLOTS:
# Digital filter frequency response
w_n, h_n = freqz(num_d, den_d, worN=w_d, fs=FS)
# Plot digital graph
# - Magnitude
plt.subplot(2, 1, 1)
plt.semilogx(w_n, 20 * np.log10(abs(h_n)), '--')
plt.legend(['Analog', 'Bilinear'])
# - Phase
plt.subplot(2, 1, 2)
plt.semilogx(w_n, np.angle(h_n) * 180 / np.pi, '--')
plt.legend(['Analog', 'Bilinear'])
# Pole check
# New pole frequency
pole_frequency = 2
# Move to zero-pole representation
z, p, k = tf2zpk(a, b)
# Check if the function presents a pole at 0 Hz
for i in range(len(p)):
if p[i] == 0:
# Replace pole
p[i] = -pole_frequency * 2 * np.pi
print('\n' + CC.PURPLE + 'Pole at 0 Hz replaced!' + CC.END)
# Back to transfer function representation
ap, bp = zpk2tf(z, p, k)
# Analog transfer function
Hp_a = TransferFunction(ap, bp)
if PLOTS:
# Analog filter frequency response
wp_t, hp_t = freqs(ap, bp, worN=w_a)
# Plot analog graph
# - Magnitude
plt.subplot(2, 1, 1)
plt.semilogx(wp_t / (2 * np.pi), 20 * np.log10(abs(hp_t)))
# - Phase
plt.subplot(2, 1, 2)
plt.semilogx(wp_t / (2 * np.pi), np.angle(hp_t) * 180 / np.pi)
# Digital transfer function through bilinear digitisation
Hp_d = c2d(Hp_a, 1 / FS, 'bilinear')
num_d = Hp_d.num[0][0]
den_d = Hp_d.den[0][0]
if PLOTS:
# Digital filter frequency response
wp_n, hp_n = freqz(num_d, den_d, worN=w_d, fs=FS)
# Plot digital graph
# - Magnitude
plt.subplot(2, 1, 1)
plt.semilogx(wp_n, 20 * np.log10(abs(hp_n)), '--')
plt.legend(['Analog', 'Bilinear', 'Pole - Analog', 'Pole - Digital'])
# - Phase
plt.subplot(2, 1, 2)
plt.semilogx(wp_n, np.angle(hp_n) * 180 / np.pi, '--')
plt.legend(['Analog', 'Bilinear', 'Pole - Analog', 'Pole - Digital'])
if PLOTS:
plt.show()
if len(PRESERVATION_FILE_NAME) > 0:
print('\nFiltering Preservation Audio File...')
# Filter Preservation Audio File
raf = lfilter(num_d, den_d, paf, axis=0)
# Again, wavfile.write() is stupid, and you must cast everything to not destroy your ears...
raf = np.rint(raf).astype(paf.dtype)
# Save Restored Audio File
save_file(raf)
else:
# Save Restored Audio File
save_file(paf)
# End
print(CC.GREEN + CC.BOLD + "Success!" + CC.END + '\n')
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment