root/branches/new_fx_rack_and_sample_fun/gui/src/SampleEditor/SampleEditor.cpp @ 627

Revision 627, 17.8 KB (checked in by wolke, 5 years ago)

try to update sample positon ruler if sample is in loopmode

Line 
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 *
5 * http://www.hydrogen-music.org
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY, without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 *
21 */
22
23#include "SampleEditor.h"
24#include "../HydrogenApp.h"
25#include "InstrumentEditor/InstrumentEditor.h"
26#include "InstrumentEditor/InstrumentEditorPanel.h"
27#include "../widgets/Button.h"
28
29#include "MainSampleWaveDisplay.h"
30#include "DetailWaveDisplay.h"
31#include "TargetWaveDisplay.h"
32
33#include <hydrogen/data_path.h>
34#include <hydrogen/h2_exception.h>
35#include <hydrogen/Preferences.h>
36#include <hydrogen/sample.h>
37#include <hydrogen/audio_engine.h>
38#include <hydrogen/hydrogen.h>
39
40#include <QModelIndex>
41#include <QTreeWidget>
42#include <QMessageBox>
43#include <algorithm>
44
45using namespace H2Core;
46using namespace std;
47
48SampleEditor::SampleEditor ( QWidget* pParent, int nSelectedLayer, QString mSamplefilename )
49                : QDialog ( pParent )
50                , Object ( "SampleEditor" )
51                , m_pSampleEditorStatus( true )
52                , m_pSamplefromFile ( NULL )
53                , m_pSelectedLayer ( nSelectedLayer )
54                , m_samplename ( mSamplefilename )
55                , m_pzoomfactor ( 1 )
56                , m_pdetailframe ( 0 )
57                , m_plineColor ( "default" )
58                , m_ponewayStart ( false )
59                , m_ponewayLoop ( false )
60                , m_ponewayEnd ( false )
61                , m_pslframes ( 0 )
62                , m_pPositionsRulerPath ( NULL )
63                , m_pPlayButton ( false )
64{
65        setupUi ( this );
66        INFOLOG ( "INIT" );
67
68        QString newfilename = mSamplefilename.section( '/', -1 );
69
70        setWindowTitle ( QString( "SampleEditor" + newfilename) );
71        setFixedSize ( width(), height() );
72        installEventFilter( this );
73
74//this new sample give us the not changed real samplelength
75        m_pSamplefromFile = Sample::load( mSamplefilename );
76
77//this
78        unsigned slframes = m_pSamplefromFile->get_n_frames();
79
80        LoopCountSpinBox->setRange(0, 20000 );
81        StartFrameSpinBox->setRange(0, slframes );
82        LoopFrameSpinBox->setRange(0, slframes );
83        EndFrameSpinBox->setRange(0, slframes );
84        EndFrameSpinBox->setValue( slframes );
85
86        getAllFrameInfos();
87        intDisplays();
88       
89
90
91//      m_pSample->set_end_frame( m_end_frame );
92
93// mainSampleview = 624(575) x 265 :-)
94// mainSampleAdjustView = 180 x 265 :-(
95// targetSampleView = 451 x 91 :-( will removed
96// StartFrameSpinBox :-)
97// LoopFrameSpinBox :-)
98// ProcessingTypeComboBox :forward, reverse, pingpong :-)
99// LoopCountSpinBox :-(
100// EndFrameSpinBox :-)
101// FadeOutFrameSpinBox :-(
102// FadeOutTypeComboBox: lin, log :-(
103// ApplyChangesPushButton :-()
104// PlayPushButton :-)
105// RestoreSamplePushButton :-(
106// ClosePushButton :-()
107// verticalzoomSlider
108
109        connect( StartFrameSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( valueChangedStartFrameSpinBox(int) ) );
110        connect( LoopFrameSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( valueChangedLoopFrameSpinBox(int) ) );
111        connect( EndFrameSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( valueChangedEndFrameSpinBox(int) ) );
112
113        m_pTimer = new QTimer(this);
114        connect(m_pTimer, SIGNAL(timeout()), this, SLOT(updateMainsamplePostionRuler()));
115}
116
117
118
119
120
121SampleEditor::~SampleEditor()
122{
123        delete m_pMainSampleWaveDisplay;
124        delete m_pSampleAdjustView;
125        delete m_pTargetSampleView;
126        delete m_pSamplefromFile;
127        INFOLOG ( "DESTROY" );
128}
129
130
131//this
132void SampleEditor::getAllFrameInfos()
133{
134        H2Core::Instrument *m_pInstrument = NULL;
135        Sample* pSample = NULL;
136        Song *pSong = Hydrogen::get_instance()->getSong();
137        if (pSong != NULL) {
138                InstrumentList *pInstrList = pSong->get_instrument_list();
139                int nInstr = Hydrogen::get_instance()->getSelectedInstrumentNumber();
140                if ( nInstr >= (int)pInstrList->get_size() ) {
141                        nInstr = -1;
142                }
143
144                if (nInstr == -1) {
145                        m_pInstrument = NULL;
146                }
147                else {
148                        m_pInstrument = pInstrList->get( nInstr );
149                        //INFOLOG( "new instr: " + m_pInstrument->m_sName );
150                }
151        }
152        H2Core::InstrumentLayer *pLayer = m_pInstrument->get_layer( m_pSelectedLayer );
153        if ( pLayer ) {
154                pSample = pLayer->get_sample();
155        }
156
157//this values are needed if we restore a sample from from disk if a new song with sample changes will load
158        m_sample_is_modified = pSample->get_sample_is_modified();
159        m_sample_mode = pSample->get_sample_mode();
160        m_start_frame = pSample->get_start_frame();
161        m_loop_frame = pSample->get_loop_frame();
162        m_repeats = pSample->get_repeats();
163        m_end_frame = m_pSamplefromFile->get_end_frame();
164
165        m_fade_out_startframe = pSample->get_fade_out_startframe();
166        m_fade_out_type = pSample->get_fade_out_type();
167       
168}
169
170
171void SampleEditor::getAllLocalFrameInfos()
172{
173        m_start_frame = StartFrameSpinBox->value();
174        m_loop_frame = LoopFrameSpinBox->value();
175        m_repeats = LoopCountSpinBox->value();
176        m_end_frame = EndFrameSpinBox->value();
177
178}
179
180void SampleEditor::intDisplays()
181{
182        H2Core::Instrument *m_pInstrument = NULL;
183        Song *pSong = Hydrogen::get_instance()->getSong();
184        if (pSong != NULL) {
185                InstrumentList *pInstrList = pSong->get_instrument_list();
186                int nInstr = Hydrogen::get_instance()->getSelectedInstrumentNumber();
187                if ( nInstr >= (int)pInstrList->get_size() ) {
188                        nInstr = -1;
189                }
190
191                if (nInstr == -1) {
192                        m_pInstrument = NULL;
193                }
194                else {
195                        m_pInstrument = pInstrList->get( nInstr );
196                        //INFOLOG( "new instr: " + m_pInstrument->m_sName );
197                }
198        }
199
200        H2Core::InstrumentLayer *pLayer = m_pInstrument->get_layer( m_pSelectedLayer );
201
202        QApplication::setOverrideCursor(Qt::WaitCursor);
203
204// wavedisplays
205        m_divider = m_pSamplefromFile->get_n_frames() / 574.0F;
206        m_pMainSampleWaveDisplay = new MainSampleWaveDisplay( mainSampleview );
207        m_pMainSampleWaveDisplay->updateDisplay( m_pSamplefromFile->get_filename() );
208        m_pMainSampleWaveDisplay->move( 1, 1 );
209
210        m_pSampleAdjustView = new DetailWaveDisplay( mainSampleAdjustView );
211        m_pSampleAdjustView->updateDisplay( m_pSamplefromFile->get_filename() );
212        m_pSampleAdjustView->move( 1, 1 );
213
214        m_pTargetSampleView = new TargetWaveDisplay( targetSampleView );
215        m_pTargetSampleView->updateDisplay( pLayer );
216        m_pTargetSampleView->move( 1, 1 );
217
218
219        QApplication::restoreOverrideCursor();
220
221}
222
223
224
225
226void SampleEditor::on_ClosePushButton_clicked()
227{
228        if ( !m_pSampleEditorStatus ){
229                int err = QMessageBox::information( this, "Hydrogen", tr( "Unsaved changes left. This changes will be lost. \nAre you sure?"), tr("&Ok"), tr("&Cancel"), 0, 1 );
230                if ( err == 0 ){
231                        m_pSampleEditorStatus = true;
232                        accept();       
233                }else
234                {
235                        return;
236                }
237        }
238        accept();
239}
240
241
242
243void SampleEditor::on_PrevChangesPushButton_clicked()
244{
245        getAllLocalFrameInfos();       
246        createNewLayer();
247        m_pSampleEditorStatus = true;
248       
249}
250
251
252
253bool SampleEditor::getCloseQuestion()
254{
255        bool close = false;
256        int err = QMessageBox::information( this, "Hydrogen", tr( "Close dialog! maybe there is some unsaved work on sample.\nAre you sure?"), tr("&Ok"), tr("&Cancel"), 0, 1 );
257        if ( err == 0 ) close = true;
258        return close;
259}
260
261
262
263/*
264void SampleEditor::getAllSampleProps()
265{
266
267        m_pSample->set_sample_is_modified( m_sample_is_modified );
268        m_pSample->set_sample_mode( m_sample_mode );
269        m_pSample->set_start_frame( m_start_frame );
270        m_pSample->set_loop_frame( m_loop_frame );
271        m_pSample->set_repeats( m_repeats );
272        m_pSample->set_end_frame( m_end_frame );
273        ERRORLOG( QString("setAllSampleProps: %1").arg(m_end_frame) );
274        m_pSample->set_fade_out_startframe( m_fade_out_startframe );
275        m_pSample->set_fade_out_type( m_fade_out_type );
276
277}
278*/
279
280
281void SampleEditor::createNewLayer()
282{
283        if ( !m_pSampleEditorStatus ){
284//              getAllFrameInfos();
285               
286                Sample *editSample = Sample::load_edit_wave( m_samplename,
287                                                            m_start_frame,
288                                                            m_loop_frame,
289                                                            m_end_frame,
290                                                            m_repeats,
291                                                            m_sample_mode);
292
293                AudioEngine::get_instance()->lock( "SampeEditor::insert new sample" );
294
295                H2Core::Instrument *m_pInstrument = NULL;
296                Song *pSong = Hydrogen::get_instance()->getSong();
297                if (pSong != NULL) {
298                        InstrumentList *pInstrList = pSong->get_instrument_list();
299                        int nInstr = Hydrogen::get_instance()->getSelectedInstrumentNumber();
300                        if ( nInstr >= (int)pInstrList->get_size() ) {
301                                nInstr = -1;
302                        }
303       
304                        if (nInstr == -1) {
305                                m_pInstrument = NULL;
306                        }
307                        else {
308                                m_pInstrument = pInstrList->get( nInstr );
309                                //INFOLOG( "new instr: " + m_pInstrument->m_sName );
310                        }
311                }
312       
313                H2Core::InstrumentLayer *pLayer = m_pInstrument->get_layer( m_pSelectedLayer );
314
315                Sample *oldSample = pLayer->get_sample();
316                delete oldSample;
317       
318                // insert new sample from newInstrument
319                pLayer->set_sample( editSample );
320
321                AudioEngine::get_instance()->unlock();
322                m_pTargetSampleView->updateDisplay( pLayer );
323                }
324               
325}
326
327void SampleEditor::mouseReleaseEvent(QMouseEvent *ev)
328{
329
330}
331
332
333
334void SampleEditor::returnAllMainWaveDisplayValues()
335{
336//      QMessageBox::information ( this, "Hydrogen", trUtf8 ( "jep %1" ).arg(m_pSample->get_n_frames()));
337        m_sample_is_modified = true;
338        m_start_frame = m_pMainSampleWaveDisplay->m_pStartFramePosition * m_divider - 25 * m_divider;
339        m_loop_frame = m_pMainSampleWaveDisplay->m_pLoopFramePosition  * m_divider - 25 * m_divider;
340        m_end_frame = m_pMainSampleWaveDisplay->m_pEndFramePosition  * m_divider - 25 * m_divider ;
341
342        StartFrameSpinBox->setValue( m_start_frame );
343        LoopFrameSpinBox->setValue( m_loop_frame );
344        EndFrameSpinBox->setValue( m_end_frame );
345        m_ponewayStart = true; 
346        m_ponewayLoop = true;
347        m_ponewayEnd = true;
348}
349
350
351
352void SampleEditor::valueChangedStartFrameSpinBox( int )
353{
354        m_pdetailframe = StartFrameSpinBox->value();
355        m_plineColor = "Start";
356        if ( !m_ponewayStart ){
357                m_pMainSampleWaveDisplay->m_pStartFramePosition = StartFrameSpinBox->value() / m_divider + 25 ;
358                m_pMainSampleWaveDisplay->updateDisplayPointer();
359                m_pSampleAdjustView->setDetailSamplePosition( m_pdetailframe, m_pzoomfactor , m_plineColor);
360                m_start_frame = StartFrameSpinBox->value();
361//              m_pMainSampleWaveDisplay->testPositionFromSampleeditor();
362                               
363        }else
364        {
365                m_pSampleAdjustView->setDetailSamplePosition( m_pdetailframe, m_pzoomfactor , m_plineColor);
366                m_ponewayStart = false;
367        }
368        testPositionsSpinBoxes();
369        m_pSampleEditorStatus = false;
370        //QMessageBox::information ( this, "Hydrogen", trUtf8 ( "jep %1" ).arg(StartFrameSpinBox->value() / m_divider + 25 ));
371}
372
373
374
375void SampleEditor::valueChangedLoopFrameSpinBox( int )
376{       
377        m_pdetailframe = LoopFrameSpinBox->value();
378        m_plineColor = "Loop";
379        if ( !m_ponewayLoop ){
380                m_pMainSampleWaveDisplay->m_pLoopFramePosition = LoopFrameSpinBox->value() / m_divider + 25 ;
381                m_pMainSampleWaveDisplay->updateDisplayPointer();
382                m_pSampleAdjustView->setDetailSamplePosition( m_pdetailframe, m_pzoomfactor , m_plineColor);
383                m_loop_frame = LoopFrameSpinBox->value();
384        }else
385        {
386                m_pSampleAdjustView->setDetailSamplePosition( m_pdetailframe, m_pzoomfactor , m_plineColor);
387                m_ponewayLoop = false;
388        }
389        testPositionsSpinBoxes();
390        m_pSampleEditorStatus = false;
391}
392
393
394
395void SampleEditor::valueChangedEndFrameSpinBox( int )
396{
397        m_pdetailframe = EndFrameSpinBox->value();
398        m_plineColor = "End";
399        if ( !m_ponewayEnd ){
400                m_pMainSampleWaveDisplay->m_pEndFramePosition = EndFrameSpinBox->value() / m_divider + 25 ;
401                m_pMainSampleWaveDisplay->updateDisplayPointer();
402                m_pSampleAdjustView->setDetailSamplePosition( m_pdetailframe, m_pzoomfactor , m_plineColor);
403                m_end_frame = EndFrameSpinBox->value();
404        }else
405        {
406                m_ponewayEnd = false;
407                m_pSampleAdjustView->setDetailSamplePosition( m_pdetailframe, m_pzoomfactor , m_plineColor);
408        }
409        testPositionsSpinBoxes();
410        m_pSampleEditorStatus = false;
411}
412
413
414void SampleEditor::on_PlayPushButton_clicked()
415{
416
417        const int selectedlayer = InstrumentEditorPanel::getInstance()->getselectedLayer();
418        const float pan_L = 0.5f;
419        const float pan_R = 0.5f;
420        const int nLength = -1;
421        const float fPitch = 0.0f;
422        Song *pSong = Hydrogen::get_instance()->getSong();
423       
424        Instrument *pInstr = pSong->get_instrument_list()->get( Hydrogen::get_instance()->getSelectedInstrumentNumber() );
425       
426        Note *pNote = new Note( pInstr, 0, pInstr->get_layer( selectedlayer )->get_end_velocity() - 0.01, pan_L, pan_R, nLength, fPitch);
427        AudioEngine::get_instance()->get_sampler()->note_on(pNote);
428
429
430        setSamplelengthFrames();
431        createPositionsRulerPath();
432        m_pPlayButton = true;
433        m_pMainSampleWaveDisplay->paintLocatorEvent( StartFrameSpinBox->value() / m_divider + 24 , true);
434        m_pSampleAdjustView->setDetailSamplePosition( m_start_frame, m_pzoomfactor , 0);
435        m_pTimer->start(40);    // update ruler at 25 fps       
436        m_prealtimeframeend = Hydrogen::get_instance()->getRealtimeFrames() + m_end_frame - m_start_frame;
437       
438}
439
440void SampleEditor::on_PlayOrigPushButton_clicked()
441{
442        Sample *pNewSample = Sample::load( m_samplename );
443        if ( pNewSample ){
444                int length = ( ( pNewSample->get_n_frames() / pNewSample->get_sample_rate() + 1) * 100 );
445                AudioEngine::get_instance()->get_sampler()->preview_sample( pNewSample, length );
446        }
447
448        m_pslframes = pNewSample->get_n_frames();
449        m_pMainSampleWaveDisplay->paintLocatorEvent( StartFrameSpinBox->value() / m_divider + 24 , true);
450        m_pSampleAdjustView->setDetailSamplePosition( m_start_frame, m_pzoomfactor , 0);
451        m_pTimer->start(40);    // update ruler at 25 fps       
452        m_prealtimeframeend = Hydrogen::get_instance()->getRealtimeFrames() + m_pslframes;
453}
454
455
456void SampleEditor::updateMainsamplePostionRuler()
457{
458        unsigned long realpos = Hydrogen::get_instance()->getRealtimeFrames();
459        if ( realpos < m_prealtimeframeend ){
460                unsigned frame = m_pslframes - ( m_prealtimeframeend  - realpos );
461                if ( m_pPlayButton == true){
462                        m_pMainSampleWaveDisplay->paintLocatorEvent( m_pPositionsRulerPath[frame] / m_divider + 25 , true);
463                        m_pSampleAdjustView->setDetailSamplePosition( m_pPositionsRulerPath[frame], m_pzoomfactor , 0);
464                        ERRORLOG(QString("frames  %1").arg(frame));     
465                        ERRORLOG(QString("m_pPositionsRulerPath  %1").arg(m_pPositionsRulerPath[frame]));
466                }else{
467                        m_pMainSampleWaveDisplay->paintLocatorEvent( frame / m_divider + 25 , true);
468                        m_pSampleAdjustView->setDetailSamplePosition( frame, m_pzoomfactor , 0);
469                }
470//              ERRORLOG( QString("sampleval: %1").arg(frame) );
471        }else
472        {
473                m_pMainSampleWaveDisplay->paintLocatorEvent( -1 , false);
474//              m_pSampleAdjustView->setDetailSamplePosition( 0, m_pzoomfactor , 0);
475                m_pTimer->stop();
476                m_pPlayButton = false;
477        }
478}
479
480void SampleEditor::createPositionsRulerPath()
481{
482        setSamplelengthFrames();
483
484        unsigned onesamplelength =  m_end_frame - m_start_frame;
485        unsigned looplength =  m_end_frame - m_loop_frame;
486        unsigned repeatslength = looplength * m_repeats;
487        unsigned newlength = 0;
488        if (onesamplelength == looplength){     
489                newlength = onesamplelength + onesamplelength * m_repeats ;
490        }else
491        {
492                newlength =onesamplelength + repeatslength;
493        }
494
495        unsigned  normallength = m_pSamplefromFile->get_n_frames();
496
497        unsigned *normalframes = new unsigned[ normallength ];
498
499
500        for ( long int i = 0; i < normallength; i++ ) {
501                normalframes[i] = i;
502        }
503
504
505        unsigned *tempframes = new unsigned[ newlength ];
506        unsigned *loopframes = new unsigned[ looplength ];
507
508        QString loopmode = m_sample_mode;
509        long int z = m_loop_frame;
510        long int y = m_start_frame;
511
512        for ( unsigned i = 0; i < newlength; i++){ //first vector
513                tempframes[i] = 0;
514        }
515
516        for ( unsigned i = 0; i < onesamplelength; i++, y++){ //first vector
517
518                tempframes[i] = normalframes[y];
519        }
520
521        for ( unsigned i = 0; i < looplength; i++, z++){ //loop vector
522
523                loopframes[i] = normalframes[z];
524        }
525
526               
527        if ( loopmode == "reverse" ){
528                reverse(loopframes, loopframes + looplength);
529        }
530
531        if ( loopmode == "reverse" && m_repeats > 0 && m_start_frame == m_loop_frame ){
532                reverse( tempframes, tempframes + onesamplelength );           
533                }
534
535        if ( loopmode == "pingpong" &&  m_start_frame == m_loop_frame){
536                reverse(loopframes, loopframes + looplength);
537        }
538       
539        for ( int i = 0; i< m_repeats ;i++){                   
540                unsigned tempdataend = onesamplelength + ( looplength * i );
541                copy( loopframes, loopframes+looplength ,tempframes+ tempdataend );
542                if ( loopmode == "pingpong" && m_repeats > 1){
543                        reverse(loopframes, loopframes + looplength);
544                }
545
546        }
547
548       
549        if ( m_repeats == 0 && loopmode == "reverse" ){
550                reverse( tempframes + m_loop_frame, tempframes + newlength);           
551                }
552
553        m_pPositionsRulerPath = tempframes;     
554
555
556}
557
558void SampleEditor::setSamplelengthFrames()
559{
560        getAllLocalFrameInfos();
561//      getAllFrameInfos();
562
563        //create new  sample length
564        unsigned onesamplelength =  m_end_frame - m_start_frame;
565        unsigned looplength =  m_end_frame - m_loop_frame ;
566        unsigned repeatslength = looplength * m_repeats;
567        unsigned newlength = 0;
568        if (onesamplelength == looplength){     
569                newlength = onesamplelength + onesamplelength * m_repeats ;
570        }else
571        {
572                newlength =onesamplelength + repeatslength;
573        }
574        m_pslframes = newlength;
575}
576
577
578
579void SampleEditor::on_LoopCountSpinBox_valueChanged( int )
580{
581        m_repeats = LoopCountSpinBox->value() ;
582        m_pSampleEditorStatus = false;
583}
584
585
586void SampleEditor::on_ProcessingTypeComboBox_currentIndexChanged( int )
587{
588        switch ( ProcessingTypeComboBox->currentIndex() ){
589                case 0 ://
590                        m_sample_mode = "forward";
591                        break;
592                case 1 ://
593                        m_sample_mode = "reverse";
594                        break;
595                case 2 ://
596                        m_sample_mode = "pingpong";
597                        break;
598                default:
599                        m_sample_mode = "forward";
600        }
601        m_pSampleEditorStatus = false;
602}
603
604
605
606
607void SampleEditor::on_verticalzoomSlider_valueChanged( int value )
608{
609        m_pzoomfactor = value / 10 +1;
610        m_pSampleAdjustView->setDetailSamplePosition( m_pdetailframe, m_pzoomfactor, m_plineColor );
611}
612
613
614void SampleEditor::testPositionsSpinBoxes()
615{
616//m_start_frame;
617//m_loop_frame;
618//m_end_frame;
619        if (  m_start_frame > m_loop_frame ) m_loop_frame = m_start_frame;
620        if (  m_start_frame > m_end_frame ) m_end_frame = m_start_frame;
621        if (  m_loop_frame > m_end_frame ) m_end_frame = m_loop_frame;
622        if (  m_end_frame < m_loop_frame ) m_loop_frame = m_end_frame;
623        if (  m_end_frame < m_start_frame ) m_start_frame = m_end_frame;
624        StartFrameSpinBox->setValue( m_start_frame );
625        LoopFrameSpinBox->setValue( m_loop_frame );
626        EndFrameSpinBox->setValue( m_end_frame );
627}
Note: See TracBrowser for help on using the browser.