Updated documentation and improved offitracker.py code
This commit is contained in:
parent
80961a5d3f
commit
c5e72e662d
@ -37,4 +37,4 @@ Examples can be found in the `example` folder.
|
||||
|
||||
A utility for converting midi files to csv can be found in the utility folder (monophonic only).
|
||||
|
||||
For usage information, check the contents of the `offitracker.py` file
|
||||
Documentation on how to use offitracker as a library and on creating songs compatible with offitracker can be found in the `docs` folder.
|
||||
|
73
docs/creating-songs.md
Normal file
73
docs/creating-songs.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Creating songs in the OffiTracker format
|
||||
|
||||
This document contains a quick tutorial on how to use the different features of the OffiTracker format to create music.
|
||||
|
||||
## Spreadsheets galore
|
||||
|
||||
The first step for your song is to create a new spreadsheet a program of your choice.
|
||||
|
||||
It should be in the CSV format with comma separated columns.
|
||||
|
||||
## Channel your creativity
|
||||
|
||||
Like any tracker, OffiTracker uses multiple channels for playing different tones at once.
|
||||
|
||||
Currently we only support square waves but this is subject to change soon. We will keep compatibility with existing songs however by making square waves the default.
|
||||
|
||||
Each channel has a `Frequency` and an `Effect` column. `Frequency` is the notes frequency in Hz and `Effect` the pulse width of your square wave.
|
||||
|
||||
You start counting your channels at 1 and can add as many as you like, just note that having more than 8 channels may result in bad audio quality or artifacting.
|
||||
|
||||
Example:
|
||||
|
||||
| Frequency1 | Effect1 | Frequency2 | Effect2 |
|
||||
| ---------- | ------- | ---------- | ------- |
|
||||
| 440 | 50 | 318 | 35 |
|
||||
|
||||
## A matter of time
|
||||
|
||||
The example above can obviously not work by itself yet as there is no way of knowing for how long to play the notes.
|
||||
|
||||
For this, you use the `Duration` column.
|
||||
|
||||
This column is recommended to be the furthest right one for consistency.
|
||||
|
||||
`Duration` stores the time your row is played for in milliseconds.
|
||||
|
||||
Example:
|
||||
|
||||
| Frequency1 | Effect1 | Frequency2 | Effect2 | Duration |
|
||||
| ---------- | ------- | ---------- | ------- | -------- |
|
||||
| 440 | 50 | 318 | 35 | 80 |
|
||||
| 519 | 50 | 411 | 28 | 114 |
|
||||
|
||||
## Noisy company
|
||||
|
||||
Having square waves is all fun and games but a good song also has drums.
|
||||
|
||||
Introducing: The `Noise` column.
|
||||
|
||||
The `Noise` column unlike the previously shown columns is optional. You do not need to include it in your file if you don't want to use it.
|
||||
|
||||
There are 5 different noises that you can play back at the start of your row:
|
||||
|
||||
1. Bass drum
|
||||
|
||||
2. Kick drum
|
||||
|
||||
3. Click
|
||||
|
||||
4. Snare
|
||||
|
||||
5. Hihat
|
||||
|
||||
A value of 0 or no value will mean that no noise is played.
|
||||
|
||||
Example:
|
||||
|
||||
| Frequency1 | Effect1 | Frequency2 | Effect2 | Noise | Duration |
|
||||
| ---------- | ------- | ---------- | ------- | ----- | -------- |
|
||||
| 440 | 50 | 318 | 35 | 0 | 80 |
|
||||
| 519 | 50 | 411 | 28 | 3 | 114 |
|
||||
|
||||
Noises have their own duration ranging from long to short depending on the noise. In case the duration set in the `Duration` column is shorter than the noise, the noise will be cut off.
|
91
docs/library-usage.md
Normal file
91
docs/library-usage.md
Normal file
@ -0,0 +1,91 @@
|
||||
# OffiTracker as a python library
|
||||
|
||||
The OffiTracker program is designed in a way that allows it to be integrated in other projects by importing it.
|
||||
|
||||
A reference design for this use case can be found in the `offiplayergui.py` file.
|
||||
|
||||
## Importing
|
||||
|
||||
The OffiTracker library can be imported by putting the line
|
||||
|
||||
```python
|
||||
import offitracker
|
||||
```
|
||||
|
||||
in the head of your python project. For this to work, you need to place the `offitracker.py` file in your projects directory as well as the `drums` folder.
|
||||
|
||||
## The stop signal
|
||||
|
||||
Before we start playing anything, it would be useful to know how to stop the playback again.
|
||||
|
||||
For that, OffiTracker has a `stop_signal` variable which we can change from outside.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
import offitracker as oftr
|
||||
|
||||
# Set stop signal to False to allow for playback
|
||||
oftr.stop_signal = False
|
||||
|
||||
# Set stop signal to True to stop playback
|
||||
oftr.stop_signal = True
|
||||
```
|
||||
|
||||
## Playing a csv file
|
||||
|
||||
Playing a csv file in the OffiTracker format can be done using the `play_csv_file` function.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
import offitracker as oftr
|
||||
|
||||
# Set stop signal to False to allow for playback
|
||||
oftr.stop_signal = False
|
||||
# Start playing back a file
|
||||
oftr.play_csv_file("example.csv")
|
||||
```
|
||||
|
||||
## Playback position, threading
|
||||
|
||||
The `play_csv_file` function has an optional `playback_row_index` variable that stores the currently playing row. If the variable is not initialized, the function will print a status message to stdout. Initializing it will disable that message and instead store the value which is useful when writing more complex software around the library.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
import offitracker as oftr
|
||||
import threading
|
||||
import time
|
||||
|
||||
def playback_thread(csv_file_path):
|
||||
# Set stop signal to False to allow for playback
|
||||
oftr.stop_signal = False
|
||||
# Initialise playback row index
|
||||
oftr.playback_row_index = 0
|
||||
# Start playing back the file
|
||||
oftr.play_csv_file(csv_file_path)
|
||||
|
||||
# Example CSV file path
|
||||
csv_file_path = "example.csv"
|
||||
|
||||
# Create a thread for playback
|
||||
playback_thread = threading.Thread(target=playback_thread, args=(csv_file_path,))
|
||||
|
||||
try:
|
||||
# Start the playback thread
|
||||
playback_thread.start()
|
||||
|
||||
while not oftr.stop_signal:
|
||||
# Read the current row index
|
||||
current_row_index = oftr.playback_row_index
|
||||
print(f"Current Row Index: {current_row_index}")
|
||||
time.sleep(1) # Adjust the sleep duration as needed
|
||||
|
||||
except KeyboardInterrupt:
|
||||
# Set stop signal to True to stop playback when Ctrl+C is pressed
|
||||
oftr.stop_signal = True
|
||||
playback_thread.join() # Wait for the playback thread to finish
|
||||
|
||||
print("Playback stopped.")
|
||||
```
|
@ -5,20 +5,9 @@ import sounddevice as sd
|
||||
import os
|
||||
|
||||
# OffiTracker, the tracker that no one asked for but I made it anyways :3
|
||||
# Usage: Make a CSV table in Excel or LibreOffice with the following format:
|
||||
# Frequency1 Effect1 Frequency2 Effect2 .... Noise Duration
|
||||
# You can make as many channels as you want.
|
||||
# Effect = pulse width from 0 to 100
|
||||
# Frequency = tone in Hz.
|
||||
# Noise:
|
||||
# - 0 = No extra sound
|
||||
# - 1 = Bass drum
|
||||
# - 2 = Kick drum
|
||||
# - 3 = Click
|
||||
# - 4 = Snare
|
||||
# - 5 = Hihat
|
||||
# Duration = tone duration in ms
|
||||
# This has started off as a silly little joke program, I never thought it would turn into such a complex little beast of a python project.
|
||||
# (c) 2024 mueller_minki, Feel free to modify or share.
|
||||
|
||||
stop_signal = False
|
||||
|
||||
noise_data_cache = {} # Cache to store loaded noise data
|
||||
@ -69,9 +58,11 @@ def play_square_waves(output_stream, frequencies, effects, duration, amplitude=1
|
||||
|
||||
output_stream.write(combined_wave)
|
||||
|
||||
def play_csv_file(file_path):
|
||||
def play_csv_file(file_path, start_row=None, stop_row=None):
|
||||
global stop_signal
|
||||
global noise_data_cache
|
||||
if 'playback_row_index' in locals():
|
||||
global playback_row_index
|
||||
|
||||
# Load all noise data into the cache
|
||||
load_all_noise_data()
|
||||
@ -81,18 +72,35 @@ def play_csv_file(file_path):
|
||||
header = csv_reader.fieldnames
|
||||
num_columns = len(header)
|
||||
num_pairs = (num_columns - 1) // 2
|
||||
total_rows = sum(1 for _ in csv_reader) # Count the total number of rows
|
||||
|
||||
# Reset the file pointer to the beginning
|
||||
csv_file.seek(0)
|
||||
next(csv_reader) # Skip the header
|
||||
|
||||
with sd.OutputStream(channels=1) as output_stream:
|
||||
for row in csv_reader:
|
||||
for idx, row in enumerate(csv_reader):
|
||||
if start_row is not None and idx < start_row:
|
||||
continue
|
||||
if stop_row is not None and idx > stop_row:
|
||||
break
|
||||
|
||||
frequencies = [float(row[f'Frequency{i}']) for i in range(1, num_pairs + 1)]
|
||||
effects = [float(row[f'Effect{i}']) for i in range(1, num_pairs + 1)]
|
||||
duration = float(row['Duration'])
|
||||
|
||||
# Check if 'Noise' column exists in the CSV file
|
||||
noise_amplitude = float(row.get('Noise', 0))
|
||||
|
||||
# Update row info
|
||||
if 'playback_row_index' in globals():
|
||||
playback_row_index = idx
|
||||
else:
|
||||
print(f"\rRow {idx + 1} of {total_rows}", end='', flush=True)
|
||||
|
||||
if stop_signal == False:
|
||||
play_square_waves(output_stream, frequencies, effects, duration, noise_amplitude=noise_amplitude)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(' ')
|
||||
print(' Mueller\'s Software Domain proudly presents:')
|
||||
@ -102,10 +110,15 @@ if __name__ == "__main__":
|
||||
print('/ | \ | | | | | | | | | \// __ \\\\ \___| <\ ___/| | \/')
|
||||
print('\_______ /__| |__| |__| |____| |__| (____ /\___ >__|_ \\\\___ >__| ')
|
||||
print(' \/ \/ \/ \/ \/ ')
|
||||
print(' Version 1.3')
|
||||
print(' Version 1.4')
|
||||
if len(sys.argv) > 1:
|
||||
csv_file_path = sys.argv[1]
|
||||
else:
|
||||
csv_file_path = input("Choose a CSV file: ")
|
||||
play_csv_file(csv_file_path)
|
||||
|
||||
# These should not be set in player mode
|
||||
start_row = None
|
||||
stop_row = None
|
||||
|
||||
play_csv_file(csv_file_path, start_row=start_row, stop_row=stop_row)
|
||||
print("\nPlayback complete.")
|
||||
|
Loading…
Reference in New Issue
Block a user