Separate out the model for the instrument list.

And populate it as instruments are coming in to not block the main thread.
startup
Ben Niemann 2019-06-02 20:40:45 +02:00
parent a6e06da89e
commit 947af52e87
7 changed files with 349 additions and 308 deletions

View File

@ -82,16 +82,17 @@ class InstrumentDBClient(object):
async def start_scan(self) -> None:
await self.__stub.call('START_SCAN')
def __handle_mutation(
async def __handle_mutation(
self,
request: instrument_db_pb2.Mutations,
response: empty_message_pb2.EmptyMessage,
) -> None:
for mutation in request.mutations:
logger.info("Mutation received: %s", mutation)
for idx, mutation in enumerate(request.mutations):
if mutation.WhichOneof('type') == 'add_instrument':
self.__instruments[mutation.add_instrument.uri] = mutation.add_instrument
else:
raise ValueError(mutation)
self.mutation_handlers.call(mutation)
if idx % 10 == 0:
await asyncio.sleep(0, loop=self.event_loop)

View File

@ -29,7 +29,8 @@ add_python_package(
flowlayout.py
gain_slider.py
instrument_library.py
instrument_library_test.py
instrument_list.py
instrument_list_test.py
load_history.py
misc.py
mute_button.py

View File

@ -51,6 +51,7 @@ from . import project_registry
from . import pipeline_perf_monitor
from . import stat_monitor
from . import settings_dialog
from . import instrument_list
from . import instrument_library
from . import ui_base
@ -137,6 +138,7 @@ class EditorApp(ui_base.AbstractEditorApp):
self.__pipeline_perf_monitor = None # type: pipeline_perf_monitor.PipelinePerfMonitor
self.__stat_monitor = None # type: stat_monitor.StatMonitor
self.default_style = None # type: str
self.instrument_list = None # type: instrument_list.InstrumentList
self.devices = None # type: device_list.DeviceList
self.setup_complete = None # type: asyncio.Event
self.__settings_dialog = None # type: settings_dialog.SettingsDialog
@ -235,7 +237,7 @@ class EditorApp(ui_base.AbstractEditorApp):
progress = win.createSetupProgress()
try:
progress.setNumSteps(4)
progress.setNumSteps(5)
logger.info("Creating StatMonitor.")
self.__stat_monitor = stat_monitor.StatMonitor(context=self.context)
@ -277,7 +279,18 @@ class EditorApp(ui_base.AbstractEditorApp):
self.show_pipeline_perf_monitor_action.setChecked)
with progress.step("Scanning instruments..."):
await self.createInstrumentDB()
create_instrument_db_response = editor_main_pb2.CreateProcessResponse()
await self.process.manager.call(
'CREATE_INSTRUMENT_DB_PROCESS', None, create_instrument_db_response)
instrument_db_address = create_instrument_db_response.address
self.instrument_db = instrument_db.InstrumentDBClient(
self.process.event_loop, self.process.server)
self.instrument_list = instrument_list.InstrumentList(context=self.context)
self.instrument_list.setup()
await self.instrument_db.setup()
await self.instrument_db.connect(instrument_db_address)
self.__instrument_library_dialog = instrument_library.InstrumentLibraryDialog(
context=self.context)
await self.__instrument_library_dialog.setup()
@ -356,6 +369,10 @@ class EditorApp(ui_base.AbstractEditorApp):
await self.urid_mapper.cleanup(self.process.event_loop)
self.urid_mapper = None
if self.instrument_list is not None:
self.instrument_list.cleanup()
self.instrument_list = None
if self.instrument_db is not None:
await self.instrument_db.disconnect()
await self.instrument_db.cleanup()
@ -418,17 +435,6 @@ class EditorApp(ui_base.AbstractEditorApp):
await self.node_db.setup()
await self.node_db.connect(node_db_address)
async def createInstrumentDB(self) -> None:
create_instrument_db_response = editor_main_pb2.CreateProcessResponse()
await self.process.manager.call(
'CREATE_INSTRUMENT_DB_PROCESS', None, create_instrument_db_response)
instrument_db_address = create_instrument_db_response.address
self.instrument_db = instrument_db.InstrumentDBClient(
self.process.event_loop, self.process.server)
await self.instrument_db.setup()
await self.instrument_db.connect(instrument_db_address)
async def createURIDMapper(self) -> None:
create_urid_mapper_response = editor_main_pb2.CreateProcessResponse()
await self.process.manager.call(

View File

@ -21,12 +21,10 @@
# @end:license
import asyncio
import bisect
import logging
import pathlib
import random
import uuid
from typing import cast, Any, Optional, Iterator, List, Tuple
from typing import cast, Any, Optional, List
from PyQt5.QtCore import Qt
from PyQt5 import QtCore
@ -34,7 +32,6 @@ from PyQt5 import QtGui
from PyQt5 import QtWidgets
from noisicaa import instrument_db
from noisicaa import core
from noisicaa import audioproc
from noisicaa import value_types
from noisicaa.builtin_nodes.instrument import processor_messages as instrument
@ -42,6 +39,7 @@ from noisicaa.builtin_nodes.midi_source import processor_messages as midi_source
from . import piano
from . import qprogressindicator
from . import ui_base
from . import instrument_list
logger = logging.getLogger(__name__)
@ -57,262 +55,8 @@ logger = logging.getLogger(__name__)
# - add/remove
class Item(object):
def __init__(self, *, parent: Optional['AbstractFolder']) -> None:
self.parent = parent
def __lt__(self, other: object) -> bool:
return self.key < cast(Item, other).key
@property
def key(self) -> Tuple[int, str, str]:
raise NotImplementedError
@property
def display_name(self) -> str:
raise NotImplementedError
def walk(self) -> Iterator['Item']:
yield self
class AbstractFolder(Item): # pylint: disable=abstract-method
def __init__(self, *, path: pathlib.Path, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.path = path
self.children = [] # type: List[Item]
def walk(self) -> Iterator['Item']:
yield from super().walk()
for child in self.children:
yield from child.walk()
class Root(AbstractFolder):
def __init__(self, **kwargs: Any) -> None:
super().__init__(parent=None, **kwargs)
@property
def key(self) -> Tuple[int, str, str]:
return (0, str(self.path).lower(), str(self.path))
@property
def display_name(self) -> str:
return '[root]'
class Folder(AbstractFolder):
@property
def key(self) -> Tuple[int, str, str]:
return (0, str(self.path).lower(), str(self.path))
@property
def display_name(self) -> str:
return str(self.path)
class Instrument(Item):
def __init__(
self, *, description: instrument_db.InstrumentDescription, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.description = description
@property
def key(self) -> Tuple[int, str, str]:
return (0, self.description.display_name.lower(), self.description.uri)
@property
def display_name(self) -> str:
return self.description.display_name
class LibraryModel(ui_base.CommonMixin, QtCore.QAbstractItemModel):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.__root_item = Root(path='/')
def cleanup(self) -> None:
self.__root_item = None
def clear(self) -> None:
self.__root_item = Root(path='/')
def addInstrument(self, description: instrument_db.InstrumentDescription) -> None:
parent = self.__root_item # type: AbstractFolder
parent_index = self.indexForItem(parent)
if description.format == instrument_db.InstrumentDescription.SF2:
folder_path = pathlib.Path(description.path)
else:
folder_path = pathlib.Path(description.path).parent
folder_parts = folder_path.parts
assert len(folder_parts) > 0, description.path
while folder_parts:
for folder_idx, folder in enumerate(parent.children):
if not isinstance(folder, AbstractFolder):
continue
match_length = 0
for idx, part in enumerate(folder.path.parts):
if part != folder_parts[idx]:
break
match_length += 1
if match_length > 0:
break
else:
folder = Folder(path=pathlib.Path(*folder_parts), parent=parent)
match_length = len(folder_parts)
folder_idx = bisect.bisect(parent.children, folder)
self.beginInsertRows(parent_index, folder_idx, folder_idx)
parent.children.insert(folder_idx, folder)
self.endInsertRows()
assert folder_parts[:match_length] == folder.path.parts[:match_length]
if match_length < len(folder.path.parts):
self.beginRemoveRows(parent_index, folder_idx, folder_idx)
old_folder = cast(Folder, parent.children.pop(folder_idx))
self.endRemoveRows()
folder = Folder(path=pathlib.Path(*folder_parts[:match_length]), parent=parent)
folder_idx = bisect.bisect(parent.children, folder)
self.beginInsertRows(parent_index, folder_idx, folder_idx)
parent.children.insert(folder_idx, folder)
self.endInsertRows()
new_folder = Folder(path=pathlib.Path(
*old_folder.path.parts[match_length:]), parent=folder)
for child in old_folder.children:
child.parent = new_folder
new_folder.children.append(child)
new_folder_idx = bisect.bisect(folder.children, folder)
self.beginInsertRows(self.indexForItem(folder), new_folder_idx, new_folder_idx)
folder.children.insert(folder_idx, new_folder)
self.endInsertRows()
folder_parts = folder_parts[match_length:]
parent = folder
parent_index = self.indexForItem(folder)
instr = Instrument(
description=description,
parent=parent)
insert_pos = bisect.bisect(parent.children, instr)
self.beginInsertRows(parent_index, insert_pos, insert_pos)
parent.children.insert(insert_pos, instr)
self.endInsertRows()
def instruments(self) -> Iterator[Instrument]:
for item in self.__root_item.walk():
if isinstance(item, Instrument):
yield item
def flattened(self, parent: Optional[AbstractFolder] = None) -> Iterator[List[str]]:
if parent is None:
parent = self.__root_item
path = [] # type: List[str]
folder = parent # type: AbstractFolder
while folder.parent is not None:
path.insert(0, folder.display_name)
folder = folder.parent
if path:
yield path
for item in parent.children:
if isinstance(item, Instrument):
yield path + [item.display_name]
elif isinstance(item, AbstractFolder):
yield from self.flattened(item)
def item(self, index: QtCore.QModelIndex) -> Item:
if not index.isValid():
raise ValueError("Invalid index")
item = index.internalPointer()
assert item is not None
return item
def indexForItem(self, item: Item, column: int = 0) -> QtCore.QModelIndex:
if item.parent is None:
return QtCore.QModelIndex()
else:
return self.createIndex(item.parent.children.index(item), column, item)
def rowCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
if parent.column() > 0: # pragma: no coverage
return 0
if not parent.isValid():
return len(self.__root_item.children)
parent_item = parent.internalPointer()
if parent_item is None:
return 0
if isinstance(parent_item, AbstractFolder):
return len(parent_item.children)
else:
return 0
def columnCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
return 1
def index(
self, row: int, column: int = 0, parent: QtCore.QModelIndex = QtCore.QModelIndex()
) -> QtCore.QModelIndex:
if not self.hasIndex(row, column, parent): # pragma: no coverage
return QtCore.QModelIndex()
if not parent.isValid():
return self.createIndex(row, column, self.__root_item.children[row])
parent_item = parent.internalPointer()
assert isinstance(parent_item, AbstractFolder), parent_item.track
item = parent_item.children[row]
return self.createIndex(row, column, item)
def parent(self, index: QtCore.QModelIndex) -> QtCore.QModelIndex: # type: ignore
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
if item is None or item.parent is None:
return QtCore.QModelIndex()
return self.indexForItem(item.parent)
def data(self, index: QtCore.QModelIndex, role: int = Qt.DisplayRole) -> Any:
if not index.isValid(): # pragma: no coverage
return None
item = index.internalPointer()
if item is None:
return None
if role in (Qt.DisplayRole, Qt.EditRole):
if index.column() == 0:
return item.display_name
return None # pragma: no coverage
def headerData(
self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole
) -> Any: # pragma: no coverage
return None
class FilterModel(QtCore.QSortFilterProxyModel):
def __init__(self, source: LibraryModel, **kwargs: Any) -> None:
def __init__(self, source: instrument_list.InstrumentList, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.setSourceModel(source)
@ -335,10 +79,10 @@ class FilterModel(QtCore.QSortFilterProxyModel):
if not parent.isValid():
return True
parent_item = cast(AbstractFolder, self.__source.item(parent))
parent_item = cast(instrument_list.AbstractFolder, self.__source.item(parent))
item = parent_item.children[row]
if isinstance(item, AbstractFolder):
if isinstance(item, instrument_list.AbstractFolder):
# Urgh... this is O(n^2)
folder_index = self.__source.index(row, 0, parent)
return any(
@ -389,8 +133,6 @@ class InstrumentLibraryDialog(ui_base.CommonMixin, QtWidgets.QDialog):
self.__instrument_id = None # type: str
self.__midi_source_id = None # type: str
self.__instrument_mutation_listener = None # type: core.Listener
self.__instrument = None # type: instrument_db.InstrumentDescription
self.__instrument_loader_task = None # type: asyncio.Task
self.__instrument_queue = asyncio.Queue(loop=self.event_loop) # type: asyncio.Queue
@ -426,8 +168,7 @@ class InstrumentLibraryDialog(ui_base.CommonMixin, QtWidgets.QDialog):
self.instruments_search.addAction(clear_action, QtWidgets.QLineEdit.TrailingPosition)
self.instruments_search.textChanged.connect(self.onInstrumentSearchChanged)
self.__model = LibraryModel(context=self.context)
self.__model_filter = FilterModel(self.__model)
self.__model_filter = FilterModel(self.app.instrument_list)
self.__view = LibraryView(self)
self.__view.setModel(self.__model_filter)
layout.addWidget(self.__view, 1)
@ -508,13 +249,6 @@ class InstrumentLibraryDialog(ui_base.CommonMixin, QtWidgets.QDialog):
async def setup(self) -> None:
logger.info("Setting up instrument library dialog...")
self.__model.clear()
for description in self.app.instrument_db.instruments:
self.__model.addInstrument(description)
self.__instrument_mutation_listener = self.app.instrument_db.mutation_handlers.add(
self.handleInstrumentMutation)
self.__instrument_id = uuid.uuid4().hex
await self.audioproc_client.add_node(
'root',
@ -574,17 +308,6 @@ class InstrumentLibraryDialog(ui_base.CommonMixin, QtWidgets.QDialog):
'root', self.__instrument_id)
self.__instrument_id = None
if self.__instrument_mutation_listener is not None:
self.__instrument_mutation_listener.remove()
self.__instrument_mutation_listener = None
def handleInstrumentMutation(self, mutation: instrument_db.Mutation) -> None:
logger.info("Mutation received: %s", mutation)
if mutation.WhichOneof('type') == 'add_instrument':
self.__model.addInstrument(mutation.add_instrument)
else:
raise TypeError(type(mutation))
async def __instrumentLoader(self) -> None:
while True:
description = await self.__instrument_queue.get()
@ -624,16 +347,16 @@ class InstrumentLibraryDialog(ui_base.CommonMixin, QtWidgets.QDialog):
self.call_async(self.app.instrument_db.start_scan())
def selectInstrument(self, uri: str) -> None:
for instr in self.__model.instruments():
for instr in self.app.instrument_list.instruments():
if instr.description.uri == uri:
index = self.__model.indexForItem(instr)
index = self.app.instrument_list.indexForItem(instr)
index = self.__model_filter.mapFromSource(index)
self.__view.setCurrentIndex(index)
def onInstrumentItemSelected(self, index: QtCore.QModelIndex) -> None:
index = self.__model_filter.mapToSource(index)
item = self.__model.item(index)
if item is not None and isinstance(item, Instrument):
item = self.app.instrument_list.item(index)
if item is not None and isinstance(item, instrument_list.Instrument):
self.__instrument_queue.put_nowait(item.description)
def onInstrumentSearchChanged(self, text: str) -> None:

View File

@ -0,0 +1,308 @@
#!/usr/bin/python3
# @begin:license
#
# Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license
import bisect
import logging
import pathlib
from typing import cast, Any, Optional, Iterator, List, Tuple
from PyQt5.QtCore import Qt
from PyQt5 import QtCore
from noisicaa import instrument_db
from noisicaa import core
from . import ui_base
logger = logging.getLogger(__name__)
class Item(object):
def __init__(self, *, parent: Optional['AbstractFolder']) -> None:
self.parent = parent
def __lt__(self, other: object) -> bool:
return self.key < cast(Item, other).key
@property
def key(self) -> Tuple[int, str, str]:
raise NotImplementedError
@property
def display_name(self) -> str:
raise NotImplementedError
def walk(self) -> Iterator['Item']:
yield self
class AbstractFolder(Item): # pylint: disable=abstract-method
def __init__(self, *, path: pathlib.Path, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.path = path
self.children = [] # type: List[Item]
def walk(self) -> Iterator['Item']:
yield from super().walk()
for child in self.children:
yield from child.walk()
class Root(AbstractFolder):
def __init__(self, **kwargs: Any) -> None:
super().__init__(parent=None, **kwargs)
@property
def key(self) -> Tuple[int, str, str]:
return (0, str(self.path).lower(), str(self.path))
@property
def display_name(self) -> str:
return '[root]'
class Folder(AbstractFolder):
@property
def key(self) -> Tuple[int, str, str]:
return (0, str(self.path).lower(), str(self.path))
@property
def display_name(self) -> str:
return str(self.path)
class Instrument(Item):
def __init__(
self, *, description: instrument_db.InstrumentDescription, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.description = description
@property
def key(self) -> Tuple[int, str, str]:
return (0, self.description.display_name.lower(), self.description.uri)
@property
def display_name(self) -> str:
return self.description.display_name
class InstrumentList(ui_base.CommonMixin, QtCore.QAbstractItemModel):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.__instrument_mutation_listener = None # type: core.Listener
self.__root_item = Root(path='/')
def setup(self) -> None:
for description in self.app.instrument_db.instruments:
self.__addInstrument(description)
self.__instrument_mutation_listener = self.app.instrument_db.mutation_handlers.add(
self.__handleInstrumentMutation)
def cleanup(self) -> None:
self.__root_item = None
if self.__instrument_mutation_listener is not None:
self.__instrument_mutation_listener.remove()
self.__instrument_mutation_listener = None
def clear(self) -> None:
self.__root_item = Root(path='/')
def __handleInstrumentMutation(self, mutation: instrument_db.Mutation) -> None:
if mutation.WhichOneof('type') == 'add_instrument':
self.addInstrument(mutation.add_instrument)
else:
raise TypeError(type(mutation))
def addInstrument(self, description: instrument_db.InstrumentDescription) -> None:
parent = self.__root_item # type: AbstractFolder
parent_index = self.indexForItem(parent)
if description.format == instrument_db.InstrumentDescription.SF2:
folder_path = pathlib.Path(description.path)
else:
folder_path = pathlib.Path(description.path).parent
folder_parts = folder_path.parts
assert len(folder_parts) > 0, description.path
while folder_parts:
for folder_idx, folder in enumerate(parent.children):
if not isinstance(folder, AbstractFolder):
continue
match_length = 0
for idx, part in enumerate(folder.path.parts):
if part != folder_parts[idx]:
break
match_length += 1
if match_length > 0:
break
else:
folder = Folder(path=pathlib.Path(*folder_parts), parent=parent)
match_length = len(folder_parts)
folder_idx = bisect.bisect(parent.children, folder)
self.beginInsertRows(parent_index, folder_idx, folder_idx)
parent.children.insert(folder_idx, folder)
self.endInsertRows()
assert folder_parts[:match_length] == folder.path.parts[:match_length]
if match_length < len(folder.path.parts):
self.beginRemoveRows(parent_index, folder_idx, folder_idx)
old_folder = cast(Folder, parent.children.pop(folder_idx))
self.endRemoveRows()
folder = Folder(path=pathlib.Path(*folder_parts[:match_length]), parent=parent)
folder_idx = bisect.bisect(parent.children, folder)
self.beginInsertRows(parent_index, folder_idx, folder_idx)
parent.children.insert(folder_idx, folder)
self.endInsertRows()
new_folder = Folder(path=pathlib.Path(
*old_folder.path.parts[match_length:]), parent=folder)
for child in old_folder.children:
child.parent = new_folder
new_folder.children.append(child)
new_folder_idx = bisect.bisect(folder.children, folder)
self.beginInsertRows(self.indexForItem(folder), new_folder_idx, new_folder_idx)
folder.children.insert(folder_idx, new_folder)
self.endInsertRows()
folder_parts = folder_parts[match_length:]
parent = folder
parent_index = self.indexForItem(folder)
instr = Instrument(
description=description,
parent=parent)
insert_pos = bisect.bisect(parent.children, instr)
self.beginInsertRows(parent_index, insert_pos, insert_pos)
parent.children.insert(insert_pos, instr)
self.endInsertRows()
def instruments(self) -> Iterator[Instrument]:
for item in self.__root_item.walk():
if isinstance(item, Instrument):
yield item
def flattened(self, parent: Optional[AbstractFolder] = None) -> Iterator[List[str]]:
if parent is None:
parent = self.__root_item
path = [] # type: List[str]
folder = parent # type: AbstractFolder
while folder.parent is not None:
path.insert(0, folder.display_name)
folder = folder.parent
if path:
yield path
for item in parent.children:
if isinstance(item, Instrument):
yield path + [item.display_name]
elif isinstance(item, AbstractFolder):
yield from self.flattened(item)
def item(self, index: QtCore.QModelIndex) -> Item:
if not index.isValid():
raise ValueError("Invalid index")
item = index.internalPointer()
assert item is not None
return item
def indexForItem(self, item: Item, column: int = 0) -> QtCore.QModelIndex:
if item.parent is None:
return QtCore.QModelIndex()
else:
return self.createIndex(item.parent.children.index(item), column, item)
def rowCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
if parent.column() > 0: # pragma: no coverage
return 0
if not parent.isValid():
return len(self.__root_item.children)
parent_item = parent.internalPointer()
if parent_item is None:
return 0
if isinstance(parent_item, AbstractFolder):
return len(parent_item.children)
else:
return 0
def columnCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
return 1
def index(
self, row: int, column: int = 0, parent: QtCore.QModelIndex = QtCore.QModelIndex()
) -> QtCore.QModelIndex:
if not self.hasIndex(row, column, parent): # pragma: no coverage
return QtCore.QModelIndex()
if not parent.isValid():
return self.createIndex(row, column, self.__root_item.children[row])
parent_item = parent.internalPointer()
assert isinstance(parent_item, AbstractFolder), parent_item.track
item = parent_item.children[row]
return self.createIndex(row, column, item)
def parent(self, index: QtCore.QModelIndex) -> QtCore.QModelIndex: # type: ignore
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
if item is None or item.parent is None:
return QtCore.QModelIndex()
return self.indexForItem(item.parent)
def data(self, index: QtCore.QModelIndex, role: int = Qt.DisplayRole) -> Any:
if not index.isValid(): # pragma: no coverage
return None
item = index.internalPointer()
if item is None:
return None
if role in (Qt.DisplayRole, Qt.EditRole):
if index.column() == 0:
return item.display_name
return None # pragma: no coverage
def headerData(
self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole
) -> Any: # pragma: no coverage
return None

View File

@ -27,10 +27,10 @@ from PyQt5 import QtCore
from noisidev import uitest
from noisicaa import instrument_db
from . import instrument_library
from . import instrument_list
class TracksModelTest(uitest.UITestCase):
class InstrumentListTest(uitest.UITestCase):
def __mkinstr(self, path):
return instrument_db.InstrumentDescription(
uri='wav:' + path,
@ -38,7 +38,7 @@ class TracksModelTest(uitest.UITestCase):
display_name=os.path.splitext(os.path.basename(path))[0])
async def test_addInstrument(self):
model = instrument_library.LibraryModel(context=self.context)
model = instrument_list.InstrumentList(context=self.context)
try:
root_index = QtCore.QModelIndex()
self.assertEqual(model.rowCount(root_index), 0)
@ -61,7 +61,7 @@ class TracksModelTest(uitest.UITestCase):
model.cleanup()
async def test_addInstrument_long_path(self):
model = instrument_library.LibraryModel(context=self.context)
model = instrument_list.InstrumentList(context=self.context)
try:
model.addInstrument(self.__mkinstr('/some/path/test1.wav'))
self.assertEqual(

View File

@ -41,6 +41,7 @@ if typing.TYPE_CHECKING:
from noisicaa import node_db as node_db_lib
from noisicaa import runtime_settings as runtime_settings_lib
from noisicaa import lv2
from . import instrument_list as instrument_list_lib
from . import project_registry
@ -263,6 +264,7 @@ class AbstractEditorApp(object):
default_style = None # type: str
qt_app = None # type: QtWidgets.QApplication
devices = None # type: device_list.DeviceList
instrument_list = None # type: instrument_list_lib.InstrumentList
def quit(self, exit_code: int = 0) -> None:
raise NotImplementedError