Skip to content

Commit cabfad3

Browse files
authored
Merge pull request CPJKU#353 from CPJKU/fluidsynth
Export wav using fluidsynth
2 parents 8918f38 + df83cc0 commit cabfad3

File tree

12 files changed

+628
-30
lines changed

12 files changed

+628
-30
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,6 @@ static
152152

153153
# phdocs
154154
phdocs.txt
155+
156+
# fluidsynth default soundfont
157+
partitura/assets/MuseScore_General.sf*

partitura/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .io.importnakamura import load_nakamuramatch, load_nakamuracorresp
2323
from .io.importparangonada import load_parangonada_csv
2424
from .io.exportparangonada import save_parangonada_csv, save_csv_for_parangonada
25-
from .io.exportaudio import save_wav
25+
from .io.exportaudio import save_wav, save_wav_fluidsynth
2626
from .io.exportmei import save_mei
2727
from .display import render
2828
from . import musicanalysis
@@ -61,5 +61,7 @@
6161
"load_nakamuracorresp",
6262
"load_parangonada_csv",
6363
"save_parangonada_csv",
64+
"save_wav",
65+
"save_wav_fluidsynth",
6466
"render",
6567
]

partitura/io/exportaudio.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#!/usr/bin/python
22
# -*- coding: utf-8 -*-
33
"""
4-
This module contains methods to synthesize Partitura object to wav using
5-
additive synthesis
4+
This module contains methods to synthesize Partitura object to wav.
65
"""
76
from typing import Union, Optional, Callable, Dict, Any
87
import numpy as np
@@ -13,10 +12,18 @@
1312
from partitura.performance import PerformanceLike
1413

1514
from partitura.utils.synth import synthesize, SAMPLE_RATE, A4
15+
from partitura.utils.fluidsynth import (
16+
synthesize_fluidsynth,
17+
DEFAULT_SOUNDFONT,
18+
HAS_FLUIDSYNTH,
19+
)
1620

1721
from partitura.utils.misc import PathLike
1822

19-
__all__ = ["save_wav"]
23+
__all__ = [
24+
"save_wav",
25+
"save_wav_fluidsynth",
26+
]
2027

2128

2229
def save_wav(
@@ -89,8 +96,76 @@ def save_wav(
8996
bpm=bpm,
9097
)
9198

99+
if out is not None:
100+
# convert to 16bit integers (save as PCM 16 bit)
101+
# (some DAWs cannot load audio files that are float64,
102+
# e.g., Logic)
103+
amplitude = np.iinfo(np.int16).max
104+
if abs(audio_signal).max() <= 1:
105+
# convert to 16bit integers (save as PCM 16 bit)
106+
amplitude = np.iinfo(np.int16).max
107+
audio_signal *= amplitude
108+
wavfile.write(out, samplerate, audio_signal.astype(np.int16))
109+
110+
else:
111+
return audio_signal
112+
113+
114+
def save_wav_fluidsynth(
115+
input_data: Union[ScoreLike, PerformanceLike, np.ndarray],
116+
out: Optional[PathLike] = None,
117+
samplerate: int = SAMPLE_RATE,
118+
soundfont: PathLike = DEFAULT_SOUNDFONT,
119+
bpm: Union[float, np.ndarray, Callable] = 60,
120+
) -> Optional[np.ndarray]:
121+
"""
122+
Export a score (a `Score`, `Part`, `PartGroup` or list of `Part` instances),
123+
a performance (`Performance`, `PerformedPart` or list of `PerformedPart` instances)
124+
as a WAV file using fluidsynth
125+
126+
Parameters
127+
----------
128+
input_data : ScoreLike, PerformanceLike or np.ndarray
129+
A partitura object with note information.
130+
out : PathLike or None
131+
Path of the output Wave file. If None, the method outputs
132+
the audio signal as an array (see `audio_signal` below).
133+
samplerate: int
134+
The sample rate of the audio file in Hz. The default is 44100Hz.
135+
soundfont : PathLike
136+
Path to the soundfont in SF2/SF3 format for fluidsynth.
137+
bpm : float, np.ndarray, callable
138+
The bpm to render the output (if the input is a score-like object).
139+
See `partitura.utils.music.performance_notearray_from_score_notearray`
140+
for more information on this parameter.
141+
142+
Returns
143+
-------
144+
audio_signal : np.ndarray
145+
Audio signal as a 1D array. Only returned if `out` is None.
146+
"""
147+
148+
if not HAS_FLUIDSYNTH:
149+
raise ImportError("Fluidsynth is not installed!")
150+
151+
audio_signal = synthesize_fluidsynth(
152+
note_info=input_data,
153+
samplerate=samplerate,
154+
soundfont=soundfont,
155+
bpm=bpm,
156+
)
157+
92158
if out is not None:
93159
# Write audio signal
94-
wavfile.write(out, samplerate, audio_signal)
160+
161+
# convert to 16bit integers (save as PCM 16 bit)
162+
# (some DAWs cannot load audio files that are float64,
163+
# e.g., Logic)
164+
amplitude = np.iinfo(np.int16).max
165+
if abs(audio_signal).max() <= 1:
166+
# convert to 16bit integers (save as PCM 16 bit)
167+
amplitude = np.iinfo(np.int16).max
168+
audio_signal *= amplitude
169+
wavfile.write(out, samplerate, audio_signal.astype(np.int16))
95170
else:
96171
return audio_signal

partitura/io/exportmatch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ def matchfile_from_alignment(
395395
onset=onset,
396396
offset=offset,
397397
velocity=pnote["velocity"],
398-
channel=pnote.get("channel", 1),
398+
channel=pnote.get("channel", 0),
399399
track=pnote.get("track", 0),
400400
)
401401
pnote_sort_info[pnote["id"]] = (

partitura/io/importmatch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ def performed_part_from_match(
394394
sound_off=midi_ticks_to_seconds(note.Offset, mpq, ppq),
395395
velocity=note.Velocity,
396396
track=getattr(note, "Track", 0),
397-
channel=getattr(note, "Channel", 1),
397+
channel=getattr(note, "Channel", 0),
398398
)
399399
)
400400
# Set first note_on to zero in ticks and seconds if first_note_at_zero

partitura/musicanalysis/performance_codec.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -819,12 +819,13 @@ def get_matched_notes(spart_note_array, ppart_note_array, alignment):
819819
else:
820820
p_id = al["performance_id"]
821821

822-
p_idx = int(np.where(ppart_note_array["id"] == p_id)[0])
822+
p_idx = np.where(ppart_note_array["id"] == p_id)[0]
823823

824824
s_idx = np.where(spart_note_array["id"] == al["score_id"])[0]
825825

826-
if len(s_idx) > 0:
826+
if len(s_idx) > 0 and len(p_idx) > 0:
827827
s_idx = int(s_idx)
828+
p_idx = int(p_idx)
828829
matched_idxs.append((s_idx, p_idx))
829830

830831
return np.array(matched_idxs)

0 commit comments

Comments
 (0)