hydrogen 1.2.3
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-2024 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
24#include <core/Basics/Song.h>
26#include <core/Basics/Pattern.h>
27
28/*
29 * Header of LilyPond file
30 * It contains the notation style (states the position of notes), and for this
31 * it follows the "Guide to Standardized Drumset Notation" by Norman Weinberg.
32 *
33 * Note that the GM-kit uses two unconventional instruments: "Stick" and
34 * "Hand Clap", so for those I did what I could and used the recommended
35 * triangle notehead to distinguish them for drum and cymbal notation.
36 */
37static const char *sHeader =
38 "\\version \"2.16.2\"\n" // Current version on Ubuntu LTS
39 "\n"
40 "#(define gmStyle\n"
41 " '(\n"
42 " (bassdrum default #f -3) ; Kick\n"
43 " (lowoodblock triangle #f 0) ; Stick\n"
44 " (snare default #f 1) ; Snare\n"
45 " (maracas triangle #f -3) ; Hand Clap\n"
46 " (highfloortom default #f -1) ; Tom Low\n"
47 " (hihat cross #f 5) ; Closed HH\n"
48 " (lowtom default #f 2) ; Tom Mid\n"
49 " (pedalhihat cross #f -5) ; Pedal HH\n"
50 " (hightom default #f 3) ; Tom Hi\n"
51 " (openhihat cross \"open\" 5) ; Open HH\n"
52 " (cowbell triangle #f 3) ; Cowbell\n"
53 " (ridecymbal cross #f 4) ; Main Ride\n"
54 " (crashcymbal cross #f 6) ; Main Crash\n"
55 " (ridecymbala cross #f 4) ; Additional Ride\n"
56 " (crashcymbala cross #f 7) ; Additional Crash\n"
57 " ))\n"
58 "\n";
59
61 m_fBPM( 0 )
62{
63}
64
66 // Retrieve metadata
67 m_sName = song.getName();
68 m_sAuthor = song.getAuthor();
69 m_fBPM = song.getBpm();
70
71 // Get the main information about the music
72 const std::vector<PatternList *> *group = song.getPatternGroupVector();
73 if ( !group || group->size() == 0 ) {
74 m_Measures.clear();
75 return;
76 }
77 unsigned nSize = group->size();
78 m_Measures = std::vector<notes_t>( nSize );
79 for ( unsigned nPatternList = 0; nPatternList < nSize; nPatternList++ ) {
80 if ( PatternList *pPatternList = ( *group )[ nPatternList ] ) {
81 addPatternList( *pPatternList, m_Measures[ nPatternList ] );
82 }
83 }
84}
85
86void H2Core::LilyPond::write( const QString &sFilename ) const {
87 std::ofstream file( sFilename.toLocal8Bit() );
88 if ( !file ) {
89 return;
90 }
91
92 file << sHeader;
93 file << "\\header {\n";
94 file << " title = \"" << m_sName.toUtf8().constData() << "\"\n";
95 file << " composer = \"" << m_sAuthor.toUtf8().constData() << "\"\n";
96 file << " tagline = \"Generated by Hydrogen " H2CORE_VERSION "\"\n";
97 file << "}\n\n";
98
99 file << "\\score {\n";
100 file << " \\new DrumStaff <<\n";
101 file << " \\set DrumStaff.drumStyleTable = #(alist->hash-table "
102 "gmStyle)\n";
103 file << " \\override Staff.TimeSignature #'style = #'() % Display "
104 "4/4 signature\n";
105 file << " \\set Staff.beamExceptions = #'() % Beam "
106 "quavers two by two\n";
107 file << " \\drummode {\n";
108 file << " \\tempo 4 = " << static_cast<int>( m_fBPM ) << "\n\n";
109 writeMeasures( file );
110 file << "\n }\n";
111 file << " >>\n";
112 file << "}\n";
113}
114
116 to.clear();
117 for ( unsigned nPattern = 0; nPattern < list.size(); nPattern++ ) {
118 if ( const Pattern *pPattern = list.get( nPattern ) ) {
119 addPattern( *pPattern, to );
120 }
121 }
122}
123
124void H2Core::LilyPond::addPattern( const Pattern &pattern, notes_t &notes ) {
125 notes.reserve( pattern.get_length() );
126 for ( unsigned nNote = 0; nNote < pattern.get_length(); nNote++ ) {
127 if ( nNote >= notes.size() ) {
128 notes.push_back( std::vector<std::pair<int, float> >() );
129 }
130
131 const Pattern::notes_t *pPatternNotes = pattern.get_notes();
132 if ( !pPatternNotes ) {
133 continue;
134 }
135 FOREACH_NOTE_CST_IT_BOUND_LENGTH( pPatternNotes, it, nNote, &pattern ) {
136 if ( Note *pNote = it->second ) {
137 int nId = pNote->get_instrument_id();
138 float fVelocity = pNote->get_velocity();
139 notes[ nNote ].push_back( std::make_pair( nId, fVelocity ) );
140 }
141 }
142 }
143}
144
145void H2Core::LilyPond::writeMeasures( std::ofstream &stream ) const {
146 unsigned nSignature = 0;
147 for ( unsigned nMeasure = 0; nMeasure < m_Measures.size(); nMeasure++ ) {
148 // Start a new measure
149 stream << "\n % Measure " << nMeasure + 1 << "\n";
150 unsigned nNewSignature = m_Measures[ nMeasure ].size() / 48;
151 if ( nSignature != nNewSignature ) { // Display time signature change
152 nSignature = nNewSignature;
153 stream << " \\time " << nSignature << "/4\n";
154 }
155
156 // Display the notes
157 stream << " << {\n";
158 writeUpper( stream, nMeasure );
159 stream << " } \\\\ {\n";
160 writeLower( stream, nMeasure );
161 stream << " } >>\n";
162 }
163}
164
165void H2Core::LilyPond::writeUpper( std::ofstream &stream,
166 unsigned nMeasure ) const {
167 // On the upper voice, we want only cymbals and mid and high toms
168 std::vector<int> whiteList;
169 whiteList.push_back( 6 ); // Closed HH
170 whiteList.push_back( 7 ); // Tom Mid
171 whiteList.push_back( 9 ); // Tom Hi
172 whiteList.push_back( 10 ); // Open HH
173 whiteList.push_back( 11 ); // Cowbell
174 whiteList.push_back( 12 ); // Ride Jazz
175 whiteList.push_back( 13 ); // Crash
176 whiteList.push_back( 14 ); // Ride Rock
177 whiteList.push_back( 15 ); // Crash Jazz
178 writeVoice( stream, nMeasure, whiteList );
179}
180
181void H2Core::LilyPond::writeLower( std::ofstream &stream,
182 unsigned nMeasure ) const {
183 std::vector<int> whiteList;
184 whiteList.push_back( 0 ); // Kick
185 whiteList.push_back( 1 ); // Stick
186 whiteList.push_back( 2 ); // Snare Jazz
187 whiteList.push_back( 3 ); // Hand Clap
188 whiteList.push_back( 4 ); // Snare Jazz
189 whiteList.push_back( 5 ); // Tom Low
190 whiteList.push_back( 8 ); // Pedal HH
191 writeVoice( stream, nMeasure, whiteList );
192}
193
195static const char *const sNames[] = { "bd", "wbl", "sn", "mar",
196 "sn", "tomfh", "hh", "toml",
197 "hhp", "tomh", "hho", "cb",
198 "cymr", "cymc", "cymra", "cymca" };
199
201static void writeNote( std::ofstream &stream, const std::vector<int> &notes ) {
202 switch ( notes.size() ) {
203 case 0: stream << "r"; break;
204 case 1: stream << sNames[ notes[ 0 ] ]; break;
205 default:
206 stream << "<";
207 for ( unsigned i = 0; i < notes.size(); i++ ) {
208 stream << sNames[ notes[ i ] ] << " ";
209 }
210 stream << ">";
211 }
212}
213
215static void writeDuration( std::ofstream &stream, unsigned duration ) {
216 if ( 48 % duration == 0 ) {
217 // This is a basic note
218 if ( duration % 2 ) {
219 return; // TODO Triplet, unsupported yet
220 }
221 stream << 4 * 48 / duration;
222
223 } else if ( duration % 3 == 0 && 48 % ( duration * 2 / 3 ) == 0 ) {
224 // This is a dotted note
225 if ( duration % 2 ) {
226 return; // TODO Triplet, unsupported yet
227 }
228 stream << 4 * 48 / ( duration * 2 / 3 ) << ".";
229
230 } else {
231 // Neither basic nor dotted, we have to split it and add a rest
232 for ( int pow = 3; pow >= 0; --pow ) {
233 if ( 3 * ( 1 << pow ) < duration ) {
234 stream << 8 * ( 3 - pow ) << " r";
235 writeDuration( stream, duration - 3 * ( 1 << pow ) );
236 break;
237 }
238 }
239 }
240}
241
242void H2Core::LilyPond::writeVoice( std::ofstream &stream,
243 unsigned nMeasure,
244 const std::vector<int> &whiteList ) const {
245 stream << " ";
246 const notes_t &measure = m_Measures[ nMeasure ];
247 for ( unsigned nStart = 0; nStart < measure.size(); nStart += 48 ) {
248 unsigned lastNote = nStart;
249 for ( unsigned nTime = nStart; nTime < nStart + 48; nTime++ ) {
250 // Get notes played at this current time
251 std::vector<int> notes;
252 const std::vector<std::pair<int, float> > &input = measure[ nTime ];
253 for ( unsigned nNote = 0; nNote < input.size(); nNote++ ) {
254 if ( std::find( whiteList.begin(),
255 whiteList.end(),
256 input[ nNote ].first ) != whiteList.end() ) {
257 notes.push_back( input[ nNote ].first );
258 }
259 }
260
261 // Write them if there are any
262 if ( !notes.empty() || nTime == nStart ) {
263 // First write duration of last note
264 if ( nTime != nStart ) {
265 writeDuration( stream, nTime - lastNote );
266 lastNote = nTime;
267 }
268
269 // Then write next note
270 stream << " ";
271 writeNote( stream, notes );
272 }
273 }
274 writeDuration( stream, nStart + 48 - lastNote );
275 }
276 stream << "\n";
277}
static void writeNote(std::ofstream &stream, const std::vector< int > &notes)
Write duration in LilyPond format, from number of 1/48th of a beat.
Definition Lilypond.cpp:201
static void writeDuration(std::ofstream &stream, unsigned duration)
Definition Lilypond.cpp:215
static const char *const sNames[]
Write group of note (may also be a rest or a single note)
Definition Lilypond.cpp:195
static const char * sHeader
Definition Lilypond.cpp:37
#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
std::vector< std::vector< std::pair< int, float > > > notes_t
Definition Lilypond.h:64
void extractData(const Song &song)
Definition Lilypond.cpp:65
static void addPattern(const Pattern &pattern, notes_t &notes)
Definition Lilypond.cpp:124
static void addPatternList(const PatternList &list, notes_t &notes)
Definition Lilypond.cpp:115
void writeUpper(std::ofstream &stream, unsigned nMeasure) const
Write upper voice of given measure to stream.
Definition Lilypond.cpp:165
void writeLower(std::ofstream &stream, unsigned nMeasure) const
Write lower voice of given measure to stream.
Definition Lilypond.cpp:181
void writeVoice(std::ofstream &stream, unsigned nMeasure, const std::vector< int > &whiteList) const
Definition Lilypond.cpp:242
void writeMeasures(std::ofstream &stream) const
Write measures in LilyPond format to stream.
Definition Lilypond.cpp:145
void write(const QString &sFilename) const
Definition Lilypond.cpp:86
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:102
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:544
const QString & getName() const
Definition Song.h:494
float getBpm() const
Definition Song.h:484
const QString & getAuthor() const
Definition Song.h:583
#define H2CORE_VERSION
A concatenation of H2CORE_VERSION_MAJOR, H2CORE_VERSION_MINOR, H2CORE_VERSION_PATCH,...
Definition config.dox:63