Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test #52

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open

test #52

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions .idea/flask-hello-world.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
# README
This JavaScript code implements a virtual piano application with functionality for playing notes, recording user input, and managing recordings. Here's an overview of the key components and functionalities:

This is the [Flask](http://flask.pocoo.org/) [quick start](http://flask.pocoo.org/docs/1.0/quickstart/#a-minimal-application) example for [Render](https://render.com).
### Setup and Initialization
- **Frequency Map (`notesFreq`)**: Defines the frequencies for musical notes, facilitating the creation of sound based on piano key presses.
- **DOM Elements Creation**: Dynamically generates piano keys (`div` elements) for each note defined in `notesFreq` and adds them to the page.
- **Audio Context**: Initializes an `AudioContext` for managing and playing sounds.

The app in this repo is deployed at [https://flask.onrender.com](https://flask.onrender.com).
### Sound Generation
- **Oscillator and Gain Node Creation (`createOscillatorAndGainNode`)**: Creates an oscillator for generating waveforms at specific frequencies and a gain node for controlling the volume, including an ADSR envelope for natural sounding note attacks and decays.
- **Start and Stop Note Functions**: Handle starting and stopping notes based on user interactions with piano keys, updating the visual state of keys and managing oscillators to play the corresponding sounds.

## Deployment
### User Interaction
- **Mouse and Pointer Events**: Captures user interactions with piano keys through mouse and pointer events, allowing for playing notes both by clicking and by dragging across keys.
- **Recording Functionality**: Allows users to record their sequences of note presses, including the timing of each note, and provides functionality to stop recording and name the recording.

Follow the guide at https://render.com/docs/deploy-flask.
### Recording Management
- **Playback**: Plays back recorded sequences by scheduling the start and stop times of notes based on the recorded timings.
- **CRUD Operations for Recordings**: Communicates with a backend server to save, list, rename, and delete recordings. This involves sending HTTP requests and handling responses to reflect changes in the UI dynamically.

### Web Application Interactions
- **Fetching and Displaying Recordings**: Retrieves a list of saved recordings from the server and updates the UI to allow users to **play**, **rename**, or **delete** recordings.
- **Server Communication**: Uses `fetch` API to send and receive data from the server, handling both the creation of new recordings and the retrieval of existing ones.

### Considerations and Enhancements
- The application emphasizes the use of the Web Audio API for sound generation and control, showcasing how web technologies can create interactive musical experiences.
- It demonstrates handling of complex user interactions, dynamic content creation, and communication with a server-side application for persistent storage.

This code serves as a practical example of combining various web technologies to build an interactive application. It illustrates key concepts such as DOM manipulation, event handling, asynchronous JavaScript, and working with the Web Audio API.
113 changes: 111 additions & 2 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,115 @@
from flask import Flask
from flask import Flask, render_template, redirect, jsonify, request
import json
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
CORS(app)

print("start connection to db")
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://robert:LTrCCDwSsyrNhFKffv5TewRi1TQXX9Hs@dpg-cn3qur5jm4es73bmkga0-a.frankfurt-postgres.render.com/recordingsdatabase'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
print("connected to db")

class Recording(db.Model):
id = db.Column('id', db.Integer, primary_key=True)
name = db.Column(db.String(50))
data = db.Column(db.Text) # This stores JSON data as text

def serialize(self):
return {
"id": self.id,
"name": self.name,
"data": self.data
}

with app.app_context():
print("Creating database tables...")
# db.drop_all() delete all tables for dev only
db.create_all()
print("Tables created.")

# My recordings JS to Flask RESTful API
recordings = {}


@app.route('/')
def hello_world():
return 'Hello, World!'
return render_template("home.html", my_rec=recordings)


if __name__ == "__main__":
print("Starting application...")
with app.app_context():
db.create_all()
app.run(debug=True)


# SAVE, DELETE, SHOW

# Database integration



def jsonify_recordings(recordings):
result = []
for recording in recordings:
result.append({
"id": recording.id,
"name": recording.name,
"data": recording.data
})
return result



@app.route('/saveRecording', methods=["POST"])
def saveRecording():
data = request.get_json() # This is your dataOfClicks from the frontend
print(data)
if not data:
return jsonify({"error": "No data provided"}), 400

# Assuming you want to use the name as a unique identifier for now, but you could modify this
name = data.get('name', 'Unnamed Recording')
recording_data = json.dumps(data.get('clicks', [])) # Convert the clicks list to a JSON string

new_recording = Recording(name=name, data=recording_data)
db.session.add(new_recording)
db.session.commit()

return jsonify({"status": "OK", "id": new_recording.id})

@app.route('/list-recordings', methods=['GET'])
def list_recordings():
recordings = Recording.query.order_by(Recording.id).all() # Fetch all recordings from the database, + sort recordings by ID
return jsonify([recording.serialize() for recording in recordings])

@app.route('/rename-recording', methods=['POST'])
def rename_recording():
data = request.get_json()
if not data or 'id' not in data or 'newName' not in data:
return jsonify({"error": "Invalid request"}), 400

recording = Recording.query.get(data['id'])
if recording:
recording.name = data['newName']
db.session.commit()
return jsonify({"success": True, "id": recording.id, "newName": recording.name})
else:
return jsonify({"error": "Recording not found"}), 404

@app.route('/delete-recording', methods=['POST'])
def delete_recording():
data = request.get_json()
if not data or 'id' not in data:
return jsonify({"error": "Invalid request"}), 400

recording = Recording.query.get(data['id'])
if recording:
db.session.delete(recording)
db.session.commit()
return jsonify({"success": True, "id": data['id']})
else:
return jsonify({"error": "Recording not found"}), 404
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
Flask
Gunicorn
flask_cors
flask-sqlalchemy
psycopg2
116 changes: 116 additions & 0 deletions static/css/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
html, body {
overflow-x: hidden;
}

body {
width: 100%;
padding-left: 1%;
height: 100%;
margin: 5px;
background-color: #b0c4de;
}

h1 {
font-family: Georgia, 'Times New Roman', Times, serif;
margin-bottom: 70px;
font-size: 4em;
margin-top: 20px;
}

#container {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}


.piano-tile {
display: flex;
text-align: center;
align-items: flex-start;
padding-top: 19%;
justify-content: center;
user-select: none;
width: 47px;
/* Adjust the width as needed */
height: 250px;
/* Adjust the height as needed */
background-color: white;
border: 3px solid black;
font-size: 25px;
font-weight: bold;
font-family: Ensures;
color: Red;
margin: 15px 1px;
box-sizing: border-box;
/* Ensures that the border width is included in the total width and height */
}

.active {
background: linear-gradient(to top, rgb(199, 29, 29), white);
}

#rand {
font-style: italic;
}

button {
padding: 7px 14px;
color: Red;
background-color: black;
font-family: Tahoma;
font-size: 16px;
}

.recording {
font-style: italic;
font-weight: 900;
}

#rec {
margin-right: 10px;

}

li {
white-space: nowrap;
}

#database {
margin-top: 30px;
}
/* Custom table styling */
#recordingsTable {
border-collapse: collapse;
width: 100%;
box-shadow: 0 4px 8px rgba(0,0,0,0.4); /* Soft shadow around the table */
border-radius: 1rem; /* Rounded corners */
}

/* Header styling */
#recordingsTable thead th {
background-color: #007bff; /* Bootstrap primary color */
color: white;
}

/* Button styling within the table */
#recordingsTable button {
border: none;
border-radius: 0.25rem;
padding: 5px 10px;
color: white;
cursor: pointer; /* Hand icon on hover */
}

/* Specific button colors */
.play-btn { background-color: #28a745; } /* Bootstrap success color */
.rename-btn { background-color: #17a2b8; } /* Bootstrap info color */
.delete-btn { background-color: #dc3545; } /* Bootstrap danger color */

/* Hover effect for buttons */
#recordingsTable button:hover {
background-color: rgb(0,40,40);
color: red;
}
Loading