hydrogen 1.2.6
Lilypond.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#include <core/config.h>
24
25#include <QFile>
26
27#ifdef H2CORE_HAVE_QT6
28 #include <QStringConverter>
29#else
30 #include <QTextCodec>
31#endif
32
34#include <core/Basics/Song.h>
36#include <core/Basics/Pattern.h>
37
38/*
39 * Header of LilyPond file
40 * It contains the notation style (states the position of notes), and for this
41 * it follows the "Guide to Standardized Drumset Notation" by Norman Weinberg.
42 *
43 * Note that the GM-kit uses two unconventional instruments: "Stick" and
44 * "Hand Clap", so for those I did what I could and used the recommended
45 * triangle notehead to distinguish them for drum and cymbal notation.
46 */
47static const char *sHeader =
48 "\\version \"2.16.2\"\n" // Current version on Ubuntu LTS
49 "\n"
50 "#(define gmStyle\n"
51 " '(\n"
52 " (bassdrum default #f -3) ; Kick\n"
53 " (lowoodblock triangle #f 0) ; Stick\n"
54 " (snare default #f 1) ; Snare\n"
55 " (maracas triangle #f -3) ; Hand Clap\n"
56 " (highfloortom default #f -1) ; Tom Low\n"
57 " (hihat cross #f 5) ; Closed HH\n"
58 " (lowtom default #f 2) ; Tom Mid\n"
59 " (pedalhihat cross #f -5) ; Pedal HH\n"
60 " (hightom default #f 3) ; Tom Hi\n"
61 " (openhihat cross \"open\" 5) ; Open HH\n"
62 " (cowbell triangle #f 3) ; Cowbell\n"
63 " (ridecymbal cross #f 4) ; Main Ride\n"
64 " (crashcymbal cross #f 6) ; Main Crash\n"
65 " (ridecymbala cross #f 4) ; Additional Ride\n"
66 " (crashcymbala cross #f 7) ; Additional Crash\n"
67 " ))\n"
68 "\n";
69
74
76 // Retrieve metadata
77 m_sName = song.getName();
78 m_sAuthor = song.getAuthor();
79 m_fBPM = song.getBpm();
80
81 // Get the main information about the music
82 const std::vector<PatternList *> *group = song.getPatternGroupVector();
83 if ( !group || group->size() == 0 ) {
84 m_Measures.clear();
85 return;
86 }
87 unsigned nSize = group->size();
88 m_Measures = std::vector<notes_t>( nSize );
89 for ( unsigned nPatternList = 0; nPatternList < nSize; nPatternList++ ) {
90 if ( PatternList *pPatternList = ( *group )[ nPatternList ] ) {
91 addPatternList( *pPatternList, m_Measures[ nPatternList ] );
92 }
93 }
94}
95
96void H2Core::LilyPond::write( const QString &sFilename ) const {
97 QFile file( sFilename );
98 if ( ! file.open( QIODevice::WriteOnly | QIODevice::Text ) ) {
99 ERRORLOG( QString( "Unable to open file [%1] for writing" )
100 .arg( sFilename ) );
101 return;
102 }
103
104 QTextStream stream( &file );
105#ifdef H2CORE_HAVE_QT6
106 stream.setEncoding( QStringConverter::Utf8 );
107#else
108 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
109#endif
110
111 stream << sHeader;
112 stream << "\\header {\n";
113 stream << " title = \"" << m_sName << "\"\n";
114 stream << " composer = \"" << m_sAuthor << "\"\n";
115 stream << " tagline = \"Generated by Hydrogen " H2CORE_VERSION "\"\n";
116 stream << "}\n\n";
117
118 stream << "\\score {\n";
119 stream << " \\new DrumStaff <<\n";
120 stream << " \\set DrumStaff.drumStyleTable = #(alist->hash-table "
121 "gmStyle)\n";
122 stream << " \\override Staff.TimeSignature #'style = #'() % Display "
123 "4/4 signature\n";
124 stream << " \\set Staff.beamExceptions = #'() % Beam "
125 "quavers two by two\n";
126 stream << " \\drummode {\n";
127 stream << " \\tempo 4 = " << static_cast<int>( m_fBPM ) << "\n\n";
128 writeMeasures( stream );
129 stream << "\n }\n";
130 stream << " >>\n";
131 stream << "}\n";
132
133 file.close();
134}
135
137 to.clear();
138 for ( unsigned nPattern = 0; nPattern < list.size(); nPattern++ ) {
139 if ( const Pattern *pPattern = list.get( nPattern ) ) {
140 addPattern( *pPattern, to );
141 }
142 }
143}
144
145void H2Core::LilyPond::addPattern( const Pattern &pattern, notes_t &notes ) {
146 notes.reserve( pattern.get_length() );
147 for ( unsigned nNote = 0; nNote < pattern.get_length(); nNote++ ) {
148 if ( nNote >= notes.size() ) {
149 notes.push_back( std::vector<std::pair<int, float> >() );
150 }
151
152 const Pattern::notes_t *pPatternNotes = pattern.get_notes();
153 if ( !pPatternNotes ) {
154 continue;
155 }
156 FOREACH_NOTE_CST_IT_BOUND_LENGTH( pPatternNotes, it, nNote, &pattern ) {
157 if ( Note *pNote = it->second ) {
158 int nId = pNote->get_instrument_id();
159 float fVelocity = pNote->get_velocity();
160 notes[ nNote ].push_back( std::make_pair( nId, fVelocity ) );
161 }
162 }
163 }
164}
165
166void H2Core::LilyPond::writeMeasures( QTextStream &stream ) const {
167 unsigned nSignature = 0;
168 for ( unsigned nMeasure = 0; nMeasure < m_Measures.size(); nMeasure++ ) {
169 // Start a new measure
170 stream << "\n % Measure " << nMeasure + 1 << "\n";
171 unsigned nNewSignature = m_Measures[ nMeasure ].size() / 48;
172 if ( nSignature != nNewSignature ) { // Display time signature change
173 nSignature = nNewSignature;
174 stream << " \\time " << nSignature << "/4\n";
175 }
176
177 // Display the notes
178 stream << " << {\n";
179 writeUpper( stream, nMeasure );
180 stream << " } \\\\ {\n";
181 writeLower( stream, nMeasure );
182 stream << " } >>\n";
183 }
184}
185
186void H2Core::LilyPond::writeUpper( QTextStream &stream,
187 unsigned nMeasure ) const {
188 // On the upper voice, we want only cymbals and mid and high toms
189 std::vector<int> whiteList;
190 whiteList.push_back( 6 ); // Closed HH
191 whiteList.push_back( 7 ); // Tom Mid
192 whiteList.push_back( 9 ); // Tom Hi
193 whiteList.push_back( 10 ); // Open HH
194 whiteList.push_back( 11 ); // Cowbell
195 whiteList.push_back( 12 ); // Ride Jazz
196 whiteList.push_back( 13 ); // Crash
197 whiteList.push_back( 14 ); // Ride Rock
198 whiteList.push_back( 15 ); // Crash Jazz
199 writeVoice( stream, nMeasure, whiteList );
200}
201
202void H2Core::LilyPond::writeLower( QTextStream &stream,
203 unsigned nMeasure ) const {
204 std::vector<int> whiteList;
205 whiteList.push_back( 0 ); // Kick
206 whiteList.push_back( 1 ); // Stick
207 whiteList.push_back( 2 ); // Snare Jazz
208 whiteList.push_back( 3 ); // Hand Clap
209 whiteList.push_back( 4 ); // Snare Jazz
210 whiteList.push_back( 5 ); // Tom Low
211 whiteList.push_back( 8 ); // Pedal HH
212 writeVoice( stream, nMeasure, whiteList );
213}
214
216static const char *const sNames[] = { "bd", "wbl", "sn", "mar",
217 "sn", "tomfh", "hh", "toml",
218 "hhp", "tomh", "hho", "cb",
219 "cymr", "cymc", "cymra", "cymca" };
220
222static void writeNote( QTextStream &stream, const std::vector<int> &notes ) {
223 switch ( notes.size() ) {
224 case 0: stream << "r"; break;
225 case 1: stream << sNames[ notes[ 0 ] ]; break;
226 default:
227 stream << "<";
228 for ( unsigned i = 0; i < notes.size(); i++ ) {
229 stream << sNames[ notes[ i ] ] << " ";
230 }
231 stream << ">";
232 }
233}
234
236static void writeDuration( QTextStream &stream, unsigned duration ) {
237 if ( 48 % duration == 0 ) {
238 // This is a basic note
239 if ( duration % 2 ) {
240 return; // TODO Triplet, unsupported yet
241 }
242 stream << 4 * 48 / duration;
243
244 } else if ( duration % 3 == 0 && 48 % ( duration * 2 / 3 ) == 0 ) {
245 // This is a dotted note
246 if ( duration % 2 ) {
247 return; // TODO Triplet, unsupported yet
248 }
249 stream << 4 * 48 / ( duration * 2 / 3 ) << ".";
250
251 } else {
252 // Neither basic nor dotted, we have to split it and add a rest
253 for ( int pow = 3; pow >= 0; --pow ) {
254 if ( 3 * ( 1 << pow ) < duration ) {
255 stream << 8 * ( 3 - pow ) << " r";
256 writeDuration( stream, duration - 3 * ( 1 << pow ) );
257 break;
258 }
259 }
260 }
261}
262
263void H2Core::LilyPond::writeVoice( QTextStream &stream,
264 unsigned nMeasure,
265 const std::vector<int> &whiteList ) const {
266 stream << " ";
267 const notes_t &measure = m_Measures[ nMeasure ];
268 for ( unsigned nStart = 0; nStart < measure.size(); nStart += 48 ) {
269 unsigned lastNote = nStart;
270 for ( unsigned nTime = nStart; nTime < nStart + 48; nTime++ ) {
271 // Get notes played at this current time
272 std::vector<int> notes;
273 const std::vector<std::pair<int, float> > &input = measure[ nTime ];
274 for ( unsigned nNote = 0; nNote < input.size(); nNote++ ) {
275 if ( std::find( whiteList.begin(),
276 whiteList.end(),
277 input[ nNote ].first ) != whiteList.end() ) {
278 notes.push_back( input[ nNote ].first );
279 }
280 }
281
282 // Write them if there are any
283 if ( !notes.empty() || nTime == nStart ) {
284 // First write duration of last note
285 if ( nTime != nStart ) {
286 writeDuration( stream, nTime - lastNote );
287 lastNote = nTime;
288 }
289
290 // Then write next note
291 stream << " ";
292 writeNote( stream, notes );
293 }
294 }
295 writeDuration( stream, nStart + 48 - lastNote );
296 }
297 stream << "\n";
298}
static void writeNote(QTextStream &stream, const std::vector< int > &notes)
Write duration in LilyPond format, from number of 1/48th of a beat.
Definition Lilypond.cpp:222
static void writeDuration(QTextStream &stream, unsigned duration)
Definition Lilypond.cpp:236
static const char *const sNames[]
Write group of note (may also be a rest or a single note)
Definition Lilypond.cpp:216
static const char * sHeader
Definition Lilypond.cpp:47
#define ERRORLOG(x)
Definition Object.h:242
#define FOREACH_NOTE_CST_IT_BOUND_LENGTH(_notes, _it, _bound, _pattern)
Iterate over all notes in column _bound in an immutable way if it is contained in _pattern.
Definition Pattern.h:290
QString m_sName
Name of the song.
Definition Lilypond.h:103
std::vector< std::vector< std::pair< int, float > > > notes_t
Definition Lilypond.h:67
void extractData(const Song &song)
Definition Lilypond.cpp:75
void writeMeasures(QTextStream &stream) const
Write measures in LilyPond format to stream.
Definition Lilypond.cpp:166
static void addPattern(const Pattern &pattern, notes_t &notes)
Definition Lilypond.cpp:145
QString m_sAuthor
Author of the song.
Definition Lilypond.h:104
static void addPatternList(const PatternList &list, notes_t &notes)
Definition Lilypond.cpp:136
void writeLower(QTextStream &stream, unsigned nMeasure) const
Write lower voice of given measure to stream.
Definition Lilypond.cpp:202
std::vector< notes_t > m_Measures
Representation of the song.
Definition Lilypond.h:102
void writeUpper(QTextStream &stream, unsigned nMeasure) const
Write upper voice of given measure to stream.
Definition Lilypond.cpp:186
void writeVoice(QTextStream &stream, unsigned nMeasure, const std::vector< int > &whiteList) const
Definition Lilypond.cpp:263
float m_fBPM
BPM of the song.
Definition Lilypond.h:105
void write(const QString &sFilename) const
Definition Lilypond.cpp:96
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:101
PatternList is a collection of patterns.
Definition PatternList.h:43
int size() const
returns the numbers of patterns
Pattern * get(int idx)
get a pattern from the list
Pattern class is a Note container.
Definition Pattern.h:46
int get_length() const
set the denominator of the pattern
Definition Pattern.h:340
const notes_t * get_notes() const
get the virtual pattern set
Definition Pattern.h:355
std::multimap< int, Note * > notes_t
< multimap note type
Definition Pattern.h:50
Song class.
Definition Song.h:61
std::vector< PatternList * > * getPatternGroupVector()
Return a pointer to a vector storing all Pattern present in the Song.
Definition Song.h:545
const QString & getName() const
Definition Song.h:495
float getBpm() const
Definition Song.h:485
const QString & getAuthor() const
Definition Song.h:584
#define H2CORE_VERSION
A concatenation of H2CORE_VERSION_MAJOR, H2CORE_VERSION_MINOR, H2CORE_VERSION_PATCH,...
Definition config.dox:63