atsushieee commited on
Commit
4a99ffd
·
verified ·
1 Parent(s): d53fa1b

Upload folder using huggingface_hub

Browse files
.github/workflows/deploy_to_hf.yml CHANGED
@@ -21,8 +21,8 @@ jobs:
21
 
22
  - name: Install Poetry
23
  run: |
24
- curl -sSL https://install.python-poetry.org | python3 -
25
- echo "${{ runner.tool_cache }}/poetry/bin" >> $GITHUB_PATH
26
 
27
  - name: Export requirements.txt
28
  run: poetry export -f requirements.txt --output requirements.txt --without-hashes
 
21
 
22
  - name: Install Poetry
23
  run: |
24
+ curl -sSL https://install.python-poetry.org | POETRY_VERSION=1.7.1 python3 -
25
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
26
 
27
  - name: Export requirements.txt
28
  run: poetry export -f requirements.txt --output requirements.txt --without-hashes
README.md CHANGED
@@ -12,7 +12,7 @@ license: mit
12
  ---
13
  # Improvisation Lab
14
 
15
- A Python package for generating musical improvisation melodies based on music theory principles. The package specializes in creating natural-sounding melodic phrases that follow chord progressions while respecting musical rules, with real-time pitch detection for practice feedback.
16
 
17
  ## Try it out! 🚀
18
  <a href="https://huggingface.co/spaces/atsushieee/improvisation-lab" target="_blank">
@@ -21,16 +21,25 @@ A Python package for generating musical improvisation melodies based on music th
21
 
22
  Watch the demo in action:
23
 
 
 
 
 
 
 
24
  https://github.com/user-attachments/assets/fa6e11d6-7b88-4b77-aa6e-a67c0927353d
25
 
26
  Experience Improvisation Lab directly in your browser! Our interactive demo lets you:
27
 
28
- - Generate melodic phrases based on chord progressions
29
  - Practice your pitch accuracy in real-time
30
  - Get instant visual guidance for hitting the right notes
31
 
32
- Note: The demo runs on Hugging Face Spaces' free tier, which means:
 
33
 
 
 
34
  - Performance might vary depending on server availability
35
  - If you encounter any issues, try refreshing the page or coming back later
36
  - For consistent performance, consider running the package locally
@@ -38,7 +47,17 @@ Note: The demo runs on Hugging Face Spaces' free tier, which means:
38
 
39
  ## Features
40
 
41
- - Generate melodic phrases based on scales and chord progressions
 
 
 
 
 
 
 
 
 
 
42
  - Support for multiple scale types:
43
  - Major
44
  - Natural minor
@@ -57,6 +76,7 @@ Note: The demo runs on Hugging Face Spaces' free tier, which means:
57
  - Real-time pitch detection with FCPE (Fast Context-aware Pitch Estimation)
58
  - Web-based and direct microphone input support
59
 
 
60
  ## Prerequisites
61
 
62
  - Python 3.11 or higher
@@ -97,7 +117,7 @@ poetry run python main.py --app_type console
97
 
98
  The application can be customized through `config.yml` with the following options:
99
 
100
- #### Audio Settings
101
  - `sample_rate`: Audio sampling rate (default: 44100 Hz)
102
  - `buffer_duration`: Duration of audio processing buffer (default: 0.2 seconds)
103
  - `note_duration`: How long to display each note during practice (default: 3 seconds)
@@ -108,7 +128,12 @@ The application can be customized through `config.yml` with the following option
108
  - `f0_max`: Maximum frequency for the pitch detection algorithm (default: 880 Hz)
109
  - `device`: Device to use for the pitch detection algorithm (default: "cpu")
110
 
111
- #### Song Selection
 
 
 
 
 
112
  - `selected_song`: Name of the song to practice
113
  - `chord_progressions`: Dictionary of songs and their progressions
114
  - Format: `[scale_root, scale_type, chord_root, chord_type, duration]`
@@ -123,13 +148,20 @@ The application can be customized through `config.yml` with the following option
123
 
124
  ## How It Works
125
 
126
- ### Melody Generation
 
 
 
 
 
 
 
127
  The melody generation follows these principles:
128
- 1. Notes are selected based on their relationship to the current chord and scale
129
- 2. Chord tones have more freedom in movement
130
- 3. Non-chord tones are restricted to moving to adjacent scale notes
131
- 4. Phrases are connected naturally by considering the previous note
132
- 5. All generated notes stay within the specified scale
133
 
134
  ### Real-time Feedback
135
  Pitch Detection Demo:
 
12
  ---
13
  # Improvisation Lab
14
 
15
+ A Python package for practicing musical improvisation through exercises. This package allows users to generate and practice melodic phrases based on music theory principles, offering real-time pitch detection for immediate feedback. Whether you're following chord progressions or practicing intervals, Improvisation Lab helps you improve your musical skills while adhering to musical rules.
16
 
17
  ## Try it out! 🚀
18
  <a href="https://huggingface.co/spaces/atsushieee/improvisation-lab" target="_blank">
 
21
 
22
  Watch the demo in action:
23
 
24
+ ### Interval Practice: Demo
25
+
26
+ https://github.com/user-attachments/assets/6a475cf0-9a82-4103-8316-7d4485b07c2e
27
+
28
+ ### Piece Practice: Demo
29
+
30
  https://github.com/user-attachments/assets/fa6e11d6-7b88-4b77-aa6e-a67c0927353d
31
 
32
  Experience Improvisation Lab directly in your browser! Our interactive demo lets you:
33
 
34
+ - Generate melodic phrases based on chord progressions or intervals
35
  - Practice your pitch accuracy in real-time
36
  - Get instant visual guidance for hitting the right notes
37
 
38
+ ### Web Interface Features
39
+ - **Tab Switching**: Easily switch between Interval Practice and Piece Practice using tabs in the web interface. This allows you to seamlessly transition between different practice modes without leaving the page.
40
 
41
+ ### Note
42
+ The demo runs on Hugging Face Spaces' free tier, which means:
43
  - Performance might vary depending on server availability
44
  - If you encounter any issues, try refreshing the page or coming back later
45
  - For consistent performance, consider running the package locally
 
47
 
48
  ## Features
49
 
50
+ - Web-based and direct microphone input support
51
+ - Real-time pitch detection with FCPE (Fast Context-aware Pitch Estimation)
52
+ - Provides real-time feedback on pitch accuracy
53
+
54
+ ### Interval Practice: Features
55
+ - Focuses on practicing musical intervals.
56
+ - Users can select the interval and direction (up or down) to practice.
57
+
58
+ ### Piece Practice: Features
59
+ - Allows users to select a song and practice its chord progressions.
60
+ - Generate melodic phrases based on scales and chord progressions.
61
  - Support for multiple scale types:
62
  - Major
63
  - Natural minor
 
76
  - Real-time pitch detection with FCPE (Fast Context-aware Pitch Estimation)
77
  - Web-based and direct microphone input support
78
 
79
+
80
  ## Prerequisites
81
 
82
  - Python 3.11 or higher
 
117
 
118
  The application can be customized through `config.yml` with the following options:
119
 
120
+ #### Common Audio Settings
121
  - `sample_rate`: Audio sampling rate (default: 44100 Hz)
122
  - `buffer_duration`: Duration of audio processing buffer (default: 0.2 seconds)
123
  - `note_duration`: How long to display each note during practice (default: 3 seconds)
 
128
  - `f0_max`: Maximum frequency for the pitch detection algorithm (default: 880 Hz)
129
  - `device`: Device to use for the pitch detection algorithm (default: "cpu")
130
 
131
+ #### Interval Practice Settings
132
+ - `interval`: The interval to practice
133
+ - Example: For a minor second descending interval, the interval value is -1
134
+ - `num_problems`: The number of problems to practice
135
+
136
+ #### Piece Practice Settings
137
  - `selected_song`: Name of the song to practice
138
  - `chord_progressions`: Dictionary of songs and their progressions
139
  - Format: `[scale_root, scale_type, chord_root, chord_type, duration]`
 
148
 
149
  ## How It Works
150
 
151
+ ### Interval Practice: Melody Generation
152
+ The interval practice focuses on improving interval recognition and singing accuracy:
153
+ 1. Users select the interval and direction (up or down) to practice.
154
+ 2. The application generates a series of problems based on the selected interval.
155
+ 3. Real-time feedback is provided to help users match the target interval.
156
+ 4. The practice session can be customized with the number of problems and note duration.
157
+
158
+ ### Piece Practice: Melody Generation
159
  The melody generation follows these principles:
160
+ 1. Notes are selected based on their relationship to the current chord and scale.
161
+ 2. Chord tones have more freedom in movement.
162
+ 3. Non-chord tones are restricted to moving to adjacent scale notes.
163
+ 4. Phrases are connected naturally by considering the previous note.
164
+ 5. All generated notes stay within the specified scale.
165
 
166
  ### Real-time Feedback
167
  Pitch Detection Demo:
improvisation_lab/application/interval_practice/web_interval_app.py CHANGED
@@ -43,7 +43,7 @@ class WebIntervalPracticeApp(BasePracticeApp):
43
  self.results_table: List[List[Any]] = []
44
  self.progress_timer: float = 0.0
45
  self.is_auto_advance = False
46
- self.note_duration = 1.5
47
 
48
  def _process_audio_callback(self, audio_data: np.ndarray):
49
  """Process incoming audio data and update the application state.
@@ -82,13 +82,11 @@ class WebIntervalPracticeApp(BasePracticeApp):
82
  self.update_results_table()
83
  self.current_note_idx += 1
84
  if self.current_note_idx >= len(self.phrases[self.current_phrase_idx]):
85
- self.current_note_idx = 0
86
  self.current_phrase_idx += 1
87
  if self.current_phrase_idx >= len(self.phrases):
88
  self.current_phrase_idx = 0
89
- self.base_note = self.phrases[self.current_phrase_idx][
90
- self.current_note_idx
91
- ].value
92
 
93
  def handle_audio(self, audio: Tuple[int, np.ndarray]) -> Tuple[str, str, str, List]:
94
  """Handle audio input from Gradio interface.
@@ -143,11 +141,10 @@ class WebIntervalPracticeApp(BasePracticeApp):
143
  num_notes=number_problems, interval=semitone_interval
144
  )
145
  self.current_phrase_idx = 0
146
- self.current_note_idx = 0
147
  self.is_running = True
148
 
149
- present_note = self.phrases[0][0].value
150
- self.base_note = present_note
151
 
152
  if not self.audio_processor.is_recording:
153
  self.text_manager.initialize_text()
@@ -210,4 +207,4 @@ class WebIntervalPracticeApp(BasePracticeApp):
210
  result,
211
  ]
212
 
213
- self.results_table.append(new_result)
 
43
  self.results_table: List[List[Any]] = []
44
  self.progress_timer: float = 0.0
45
  self.is_auto_advance = False
46
+ self.note_duration = 3.0
47
 
48
  def _process_audio_callback(self, audio_data: np.ndarray):
49
  """Process incoming audio data and update the application state.
 
82
  self.update_results_table()
83
  self.current_note_idx += 1
84
  if self.current_note_idx >= len(self.phrases[self.current_phrase_idx]):
85
+ self.current_note_idx = 1
86
  self.current_phrase_idx += 1
87
  if self.current_phrase_idx >= len(self.phrases):
88
  self.current_phrase_idx = 0
89
+ self.base_note = self.phrases[self.current_phrase_idx][0].value
 
 
90
 
91
  def handle_audio(self, audio: Tuple[int, np.ndarray]) -> Tuple[str, str, str, List]:
92
  """Handle audio input from Gradio interface.
 
141
  num_notes=number_problems, interval=semitone_interval
142
  )
143
  self.current_phrase_idx = 0
144
+ self.current_note_idx = 1
145
  self.is_running = True
146
 
147
+ self.base_note = self.phrases[0][0].value
 
148
 
149
  if not self.audio_processor.is_recording:
150
  self.text_manager.initialize_text()
 
207
  result,
208
  ]
209
 
210
+ self.results_table.insert(0, new_result)
improvisation_lab/domain/composition/melody_composer.py CHANGED
@@ -87,5 +87,5 @@ class MelodyComposer:
87
  melody = []
88
  for base_note in base_notes:
89
  target_note = self.note_transposer.transpose_note(base_note, interval)
90
- melody.append([base_note, target_note, base_note])
91
  return melody
 
87
  melody = []
88
  for base_note in base_notes:
89
  target_note = self.note_transposer.transpose_note(base_note, interval)
90
+ melody.append([base_note, target_note])
91
  return melody
improvisation_lab/presentation/interval_practice/web_interval_view.py CHANGED
@@ -87,7 +87,7 @@ class WebIntervalPracticeView(WebPracticeView):
87
  )
88
  self.note_duration_box = gr.Number(
89
  label="Note Duration (seconds)",
90
- value=1.5,
91
  )
92
 
93
  self.generate_melody_button = gr.Button("Generate Melody")
@@ -97,6 +97,15 @@ class WebIntervalPracticeView(WebPracticeView):
97
  with gr.Row():
98
  self.phrase_info_box = gr.Textbox(label="Problem Information", value="")
99
  self.pitch_result_box = gr.Textbox(label="Pitch Result", value="")
 
 
 
 
 
 
 
 
 
100
  self.results_table = gr.DataFrame(
101
  headers=[
102
  "Problem Number",
@@ -110,9 +119,7 @@ class WebIntervalPracticeView(WebPracticeView):
110
  label="Result History",
111
  )
112
 
113
- self._add_audio_input()
114
- self.end_practice_button = gr.Button("End Practice")
115
-
116
  self._add_buttons_callbacks()
117
 
118
  # Add Tone.js script
@@ -186,21 +193,13 @@ class WebIntervalPracticeView(WebPracticeView):
186
  outputs=[self.base_note_box, self.phrase_info_box, self.pitch_result_box],
187
  )
188
 
189
- def _add_audio_input(self):
190
  """Create the audio input section."""
191
- audio_input = gr.Audio(
192
- label="Audio Input",
193
- sources=["microphone"],
194
- streaming=True,
195
- type="numpy",
196
- show_label=True,
197
- )
198
-
199
  # Attention: have to specify inputs explicitly,
200
  # otherwise the callback function is not called
201
- audio_input.stream(
202
  fn=self.on_audio_input,
203
- inputs=audio_input,
204
  outputs=[
205
  self.base_note_box,
206
  self.phrase_info_box,
 
87
  )
88
  self.note_duration_box = gr.Number(
89
  label="Note Duration (seconds)",
90
+ value=3.0,
91
  )
92
 
93
  self.generate_melody_button = gr.Button("Generate Melody")
 
97
  with gr.Row():
98
  self.phrase_info_box = gr.Textbox(label="Problem Information", value="")
99
  self.pitch_result_box = gr.Textbox(label="Pitch Result", value="")
100
+ """Create the audio input section."""
101
+ self.audio_input = gr.Audio(
102
+ label="Audio Input",
103
+ sources=["microphone"],
104
+ streaming=True,
105
+ type="numpy",
106
+ show_label=True,
107
+ )
108
+ self.end_practice_button = gr.Button("End Practice")
109
  self.results_table = gr.DataFrame(
110
  headers=[
111
  "Problem Number",
 
119
  label="Result History",
120
  )
121
 
122
+ self._add_audio_callbacks()
 
 
123
  self._add_buttons_callbacks()
124
 
125
  # Add Tone.js script
 
193
  outputs=[self.base_note_box, self.phrase_info_box, self.pitch_result_box],
194
  )
195
 
196
+ def _add_audio_callbacks(self):
197
  """Create the audio input section."""
 
 
 
 
 
 
 
 
198
  # Attention: have to specify inputs explicitly,
199
  # otherwise the callback function is not called
200
+ self.audio_input.stream(
201
  fn=self.on_audio_input,
202
+ inputs=self.audio_input,
203
  outputs=[
204
  self.base_note_box,
205
  self.phrase_info_box,
tests/domain/composition/test_melody_composer.py CHANGED
@@ -94,7 +94,7 @@ class TestMelodyComposer:
94
 
95
  # Check the length of the melody
96
  assert len(melody) == len(base_notes)
97
- assert len(melody[0]) == 3
98
 
99
  # Check the structure of the melody
100
  for i, base_note in enumerate(base_notes):
@@ -103,4 +103,3 @@ class TestMelodyComposer:
103
  base_note, interval
104
  )
105
  assert melody[i][1] == transposed_note
106
- assert melody[i][2] == base_note
 
94
 
95
  # Check the length of the melody
96
  assert len(melody) == len(base_notes)
97
+ assert len(melody[0]) == 2
98
 
99
  # Check the structure of the melody
100
  for i, base_note in enumerate(base_notes):
 
103
  base_note, interval
104
  )
105
  assert melody[i][1] == transposed_note
 
tests/service/test_interval_practice_service.py CHANGED
@@ -13,9 +13,9 @@ class TestPiecePracticeService:
13
  config = Config()
14
  service = IntervalPracticeService(config)
15
  melody = service.generate_melody(num_notes=10, interval=2)
16
- # 10 notes, each with 3 parts (base, transposed, base)
17
  assert len(melody) == 10
18
- assert all(len(note_group) == 3 for note_group in melody)
19
  assert all(
20
  isinstance(note, Notes) for note_group in melody for note in note_group
21
  )
 
13
  config = Config()
14
  service = IntervalPracticeService(config)
15
  melody = service.generate_melody(num_notes=10, interval=2)
16
+ # 10 notes, each with 2 parts (base, transposed)
17
  assert len(melody) == 10
18
+ assert all(len(note_group) == 2 for note_group in melody)
19
  assert all(
20
  isinstance(note, Notes) for note_group in melody for note in note_group
21
  )